blob: dba0ceb0b65cc493d31c058c189784a1f4a3ae93 [file] [log] [blame]
sdefresnee65fd872016-12-19 13:38:131// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/browser_view_controller.h"
6
7#import <AssetsLibrary/AssetsLibrary.h>
8#import <MobileCoreServices/MobileCoreServices.h>
9#import <PassKit/PassKit.h>
10#import <Photos/Photos.h>
11#import <QuartzCore/QuartzCore.h>
12
13#include <stdint.h>
14#include <cmath>
15#include <memory>
16
17#include "base/base64.h"
18#include "base/command_line.h"
mathp9b4c11d2017-07-06 20:24:1319#include "base/feature_list.h"
gambard9efce7a2017-02-09 18:53:1720#include "base/files/file_path.h"
sdefresnee65fd872016-12-19 13:38:1321#include "base/format_macros.h"
22#include "base/i18n/rtl.h"
23#include "base/ios/block_types.h"
24#include "base/ios/ios_util.h"
sdefresnee65fd872016-12-19 13:38:1325#include "base/logging.h"
26#include "base/mac/bind_objc_block.h"
27#include "base/mac/bundle_locations.h"
28#include "base/mac/foundation_util.h"
sdefresnee65fd872016-12-19 13:38:1329#include "base/macros.h"
30#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2931#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1332#include "base/metrics/user_metrics.h"
33#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1334#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3935#include "base/strings/utf_string_conversions.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2436#include "base/task_scheduler/post_task.h"
tzik14236032017-02-15 06:41:0137#include "base/threading/sequenced_worker_pool.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2438#include "base/threading/thread_restrictions.h"
sdefresnee65fd872016-12-19 13:38:1339#include "components/bookmarks/browser/base_bookmark_model_observer.h"
40#include "components/bookmarks/browser/bookmark_model.h"
Sylvain Defresne7178d4c2017-09-14 13:22:3741#include "components/favicon/ios/web_favicon_driver.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2342#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0343#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2344#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1145#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1346#include "components/infobars/core/infobar_manager.h"
mathp9b4c11d2017-07-06 20:24:1347#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1348#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5449#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1350#include "components/search_engines/search_engines_pref_names.h"
51#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0552#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1353#include "components/sessions/core/tab_restore_service_helper.h"
edchincd32fdf2017-10-25 12:45:4554#include "components/signin/core/browser/account_reconcilor.h"
55#include "components/signin/core/browser/signin_metrics.h"
56#import "components/signin/ios/browser/account_consistency_service.h"
Eugene Butc90499d52017-09-22 16:02:0957#include "components/signin/ios/browser/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:1358#include "components/strings/grit/components_strings.h"
59#include "components/toolbar/toolbar_model_impl.h"
60#include "ios/chrome/app/tests_hook.h"
61#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
62#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
63#include "ios/chrome/browser/chrome_url_constants.h"
64#include "ios/chrome/browser/chrome_url_util.h"
65#include "ios/chrome/browser/experimental_flags.h"
66#import "ios/chrome/browser/favicon/favicon_loader.h"
67#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2368#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
69#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1370#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
71#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3672#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1373#include "ios/chrome/browser/first_run/first_run.h"
74#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
75#include "ios/chrome/browser/infobars/infobar_container_ios.h"
76#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3077#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
sdefresnee65fd872016-12-19 13:38:1378#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
79#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1380#import "ios/chrome/browser/open_url_util.h"
81#import "ios/chrome/browser/passwords/password_controller.h"
Tomasz Garbusb844e992017-09-29 12:44:5582#include "ios/chrome/browser/passwords/password_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1383#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5484#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
85#import "ios/chrome/browser/prerender/prerender_service.h"
86#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2487#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1388#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
89#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
90#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0591#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1392#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
93#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
edchincd32fdf2017-10-25 12:45:4594#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
95#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
sdefresnee65fd872016-12-19 13:38:1396#import "ios/chrome/browser/snapshots/snapshot_cache.h"
97#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
98#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Mike Dougherty4620cf8e2017-10-31 23:37:0999#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
100#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
pkld6e73e52017-03-08 15:56:51101#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28102#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13103#import "ios/chrome/browser/tabs/tab.h"
104#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19105#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13106#import "ios/chrome/browser/tabs/tab_model.h"
107#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16108#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13109#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Rohit Rao01e0e002017-08-14 20:49:43110#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
111#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13112#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59113#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13114#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
115#import "ios/chrome/browser/ui/background_generator.h"
116#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
117#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13118#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38119#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
sdefresnee65fd872016-12-19 13:38:13120#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30121#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24122#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54123#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38124#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/commands/open_url_command.h"
126#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
edchin95c927072017-11-04 00:35:07127#import "ios/chrome/browser/ui/commands/show_signin_command.h"
edchin7f210cd2017-09-28 08:03:53128#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00129#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23130#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13131#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13132#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
133#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
134#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
135#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35136#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13137#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
138#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimoto803840622017-10-28 01:20:37139#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto62e97c72017-11-03 19:51:47140#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38141#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13142#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13143#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26144#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59145#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10146#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42148#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13149#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27150#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
151#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13152#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37153#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13154#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11155#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
156#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13157#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03158#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13160#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53161#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
edchin95c927072017-11-04 00:35:07162#import "ios/chrome/browser/ui/settings/sync_utils/sync_presenter.h"
sczs40443972017-09-13 19:02:39163#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13164#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin9e7a1112017-11-07 18:28:03165#import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
edchin7f210cd2017-09-28 08:03:53166#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13167#import "ios/chrome/browser/ui/stack_view/card_view.h"
168#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
169#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13170#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03171#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
172#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
173#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
sczsf1620e52017-10-02 22:54:46174#include "ios/chrome/browser/ui/toolbar/toolbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13175#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
176#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31177#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
sczsc2b4f152017-10-11 01:44:24178#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
sczsbbad1632017-07-29 03:48:00179#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13180#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
181#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
182#include "ios/chrome/browser/ui/ui_util.h"
183#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28184#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13185#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
186#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51187#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13188#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27189#import "ios/chrome/browser/web/load_timing_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13190#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04191#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05192#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59193#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53194#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04195#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05196#import "ios/chrome/browser/web_state_list/web_state_list.h"
197#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57198#import "ios/chrome/browser/webui/net_export_tab_helper.h"
199#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
200#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13201#include "ios/chrome/grit/ios_chromium_strings.h"
202#include "ios/chrome/grit/ios_strings.h"
203#import "ios/net/request_tracker.h"
204#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
205#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
206#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
207#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
208#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
209#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
210#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
211#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36212#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13213#include "ios/web/public/navigation_item.h"
214#import "ios/web/public/navigation_manager.h"
215#include "ios/web/public/referrer_util.h"
216#include "ios/web/public/ssl_status.h"
217#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39218#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13219#include "ios/web/public/web_client.h"
220#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13221#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29222#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19223#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13224#import "ios/web/public/web_state/web_state_delegate_bridge.h"
225#include "ios/web/public/web_thread.h"
226#import "ios/web/web_state/ui/crw_web_controller.h"
227#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17228#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13229#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
230#include "net/ssl/ssl_info.h"
231#include "net/url_request/url_request_context_getter.h"
232#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
233#include "ui/base/l10n/l10n_util.h"
234#include "ui/base/l10n/l10n_util_mac.h"
235#include "ui/base/page_transition_types.h"
236#include "url/gurl.h"
237
stkhapuginf58b10d02017-04-10 13:36:17238#if !defined(__has_feature) || !__has_feature(objc_arc)
239#error "This file requires ARC support."
240#endif
241
sdefresnee65fd872016-12-19 13:38:13242using base::UserMetricsAction;
243using bookmarks::BookmarkNode;
244
245class BrowserBookmarkModelBridge;
246class InfoBarContainerDelegateIOS;
247
sdefresnee65fd872016-12-19 13:38:13248namespace {
249
250typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
251 // Note: these values must match the ContextMenuOption enum in histograms.xml.
252 ACTION_OPEN_IN_NEW_TAB = 0,
253 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
254 ACTION_COPY_LINK_ADDRESS = 2,
255 ACTION_SAVE_IMAGE = 6,
256 ACTION_OPEN_IMAGE = 7,
257 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
258 ACTION_SEARCH_BY_IMAGE = 11,
259 ACTION_OPEN_JAVASCRIPT = 21,
260 ACTION_READ_LATER = 22,
261 NUM_ACTIONS = 23,
262};
263
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28264void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13265 if (is_image) {
266 if (is_link) {
267 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
268 NUM_ACTIONS);
269 } else {
270 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
271 NUM_ACTIONS);
272 }
273 } else {
274 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
275 NUM_ACTIONS);
276 }
277}
278
edchinf5150c682017-09-18 02:50:03279// Returns the status bar background color.
280UIColor* StatusBarBackgroundColor() {
281 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
282}
283
284// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47285const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03286
sdefresnee65fd872016-12-19 13:38:13287const CGFloat kVoiceSearchBarHeight = 59.0;
288
289// Dimensions to use when downsizing an image for search-by-image.
290const CGFloat kSearchByImageMaxImageArea = 90000.0;
291const CGFloat kSearchByImageMaxImageWidth = 600.0;
292const CGFloat kSearchByImageMaxImageHeight = 400.0;
293
sdefresnee65fd872016-12-19 13:38:13294enum HeaderBehaviour {
295 // The header moves completely out of the screen.
296 Hideable = 0,
297 // This header stays on screen and doesn't overlap with the content.
298 Visible,
299 // This header stay on screen and covers part of the content.
300 Overlap
301};
302
sdefresnee65fd872016-12-19 13:38:13303const CGFloat kIPadFindBarOverlap = 11;
304
305bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24306 // Most URLs are allowed in incognito; the following is an exception.
307 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13308}
309
edchineeb4d422017-10-02 17:39:36310// Snackbar category for browser view controller.
311NSString* const kBrowserViewControllerSnackbarCategory =
312 @"BrowserViewControllerSnackbarCategory";
313
rohitrao005a6432017-03-16 20:52:42314} // namespace
sdefresnee65fd872016-12-19 13:38:13315
stkhapugin952ecef2017-04-11 12:11:45316#pragma mark - HeaderDefinition helper
317
318@interface HeaderDefinition : NSObject
319
320// The header view.
321@property(nonatomic, strong) UIView* view;
322// How to place the view, and its behaviour when the headers move.
323@property(nonatomic, assign) HeaderBehaviour behaviour;
324// Reduces the height of a header to adjust for shadows.
325@property(nonatomic, assign) CGFloat heightAdjustement;
326// Nudges that particular header up by this number of points.
327@property(nonatomic, assign) CGFloat inset;
328
329- (instancetype)initWithView:(UIView*)view
330 headerBehaviour:(HeaderBehaviour)behaviour
331 heightAdjustment:(CGFloat)heightAdjustment
332 inset:(CGFloat)inset;
333
334+ (instancetype)definitionWithView:(UIView*)view
335 headerBehaviour:(HeaderBehaviour)behaviour
336 heightAdjustment:(CGFloat)heightAdjustment
337 inset:(CGFloat)inset;
338
339@end
340
341@implementation HeaderDefinition
342@synthesize view = _view;
343@synthesize behaviour = _behaviour;
344@synthesize heightAdjustement = _heightAdjustement;
345@synthesize inset = _inset;
346
347+ (instancetype)definitionWithView:(UIView*)view
348 headerBehaviour:(HeaderBehaviour)behaviour
349 heightAdjustment:(CGFloat)heightAdjustment
350 inset:(CGFloat)inset {
351 return [[self alloc] initWithView:view
352 headerBehaviour:behaviour
353 heightAdjustment:heightAdjustment
354 inset:inset];
355}
356
357- (instancetype)initWithView:(UIView*)view
358 headerBehaviour:(HeaderBehaviour)behaviour
359 heightAdjustment:(CGFloat)heightAdjustment
360 inset:(CGFloat)inset {
361 self = [super init];
362 if (self) {
363 _view = view;
364 _behaviour = behaviour;
365 _heightAdjustement = heightAdjustment;
366 _inset = inset;
367 }
368 return self;
369}
370
371@end
372
373#pragma mark - BVC
374
Rohit Rao01e0e002017-08-14 20:49:43375@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43376 AppRatingPromptDelegate,
Mike Dougherty4620cf8e2017-10-31 23:37:09377 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13378 CRWNativeContentProvider,
379 CRWWebStateDelegate,
380 DialogPresenterDelegate,
Kurt Horimoto62e97c72017-11-03 19:51:47381 LegacyFullscreenControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13382 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57383 NetExportTabHelperDelegate,
edchincd32fdf2017-10-25 12:45:45384 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13385 MFMailComposeViewControllerDelegate,
386 NewTabPageControllerObserver,
387 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27388 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13389 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55390 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13391 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11392 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59393 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31394 SideSwipeControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13395 SKStoreProductViewControllerDelegate,
396 SnapshotOverlayProvider,
397 StoreKitLauncher,
edchin9e7a1112017-11-07 18:28:03398 SigninPresenter,
edchin95c927072017-11-04 00:35:07399 SyncPresenter,
sdefresnee65fd872016-12-19 13:38:13400 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19401 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38402 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13403 TabModelObserver,
404 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03405 TabStripPresentation,
sdefresnee65fd872016-12-19 13:38:13406 UIGestureRecognizerDelegate,
407 UpgradeCenterClientProtocol,
408 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04409 VoiceSearchBarOwner,
410 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13411 // The dependency factory passed on initialization. Used to vend objects used
412 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27413 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13414
415 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27416 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13417
sczsf1620e52017-10-02 22:54:46418 // Facade objects used by |_toolbarCoordinator|.
419 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13420 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
421 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
422
sdefresnee65fd872016-12-19 13:38:13423 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27424 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13425
426 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27427 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13428
429 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27430 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13431
432 // Handles presentation of JavaScript dialogs.
433 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
434
justincohen75011c32017-04-28 16:31:39435 // Handles command dispatching.
436 CommandDispatcher* _dispatcher;
437
sdefresnee65fd872016-12-19 13:38:13438 // Keyboard commands provider. It offloads most of the keyboard commands
439 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27440 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13441
442 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
443 // to |-reparentToolbarController| will reset it to NO.
444 BOOL _isToolbarControllerRelinquished;
445
446 // The controller that owns the currently relinquished toolbar controller.
447 // The reference is weak because it's possible for the toolbar owner to be
448 // deallocated mid-animation due to memory pressure or a tab being closed
449 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27450 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13451
sdefresnee65fd872016-12-19 13:38:13452 // Used to inject Javascript implementing the PaymentRequest API and to
453 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27454 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13455
sdefresnee65fd872016-12-19 13:38:13456 // Used to display the Voice Search UI. Nil if not visible.
457 scoped_refptr<VoiceSearchController> _voiceSearchController;
458
gambard6299cc1d2017-02-21 13:06:03459 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27460 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03461
sdefresnee65fd872016-12-19 13:38:13462 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27463 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13464
sdefresnee65fd872016-12-19 13:38:13465 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27466 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13467
468 // Records the set of domains for which full screen alert has already been
469 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27470 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13471
472 // Adapter to let BVC be the delegate for WebState.
473 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
474
475 // YES if new tab is animating in.
476 BOOL _inNewTabAnimation;
477
478 // YES if Voice Search should be started when the new tab animation is
479 // finished.
480 BOOL _startVoiceSearchAfterNewTabAnimation;
481
482 // YES if the user interacts with the location bar.
483 BOOL _locationBarHasFocus;
484 // YES if a load was cancelled due to typing in the location bar.
485 BOOL _locationBarEditCancelledLoad;
486 // YES if waiting for a foreground tab due to expectNewForegroundTab.
487 BOOL _expectingForegroundTab;
488
Sylvain Defresne41170aa2017-06-15 10:25:20489 // Whether or not -shutdown has been called.
490 BOOL _isShutdown;
491
sdefresnee65fd872016-12-19 13:38:13492 // The ChromeBrowserState associated with this BVC.
493 ios::ChromeBrowserState* _browserState; // weak
494
495 // Whether or not Incognito* is enabled.
496 BOOL _isOffTheRecord;
497
498 // The last point within |_contentArea| that's received a touch.
499 CGPoint _lastTapPoint;
500
501 // The time at which |_lastTapPoint| was most recently set.
502 CFTimeInterval _lastTapTime;
503
504 // A single infobar container handles all infobars in all tabs. It keeps
505 // track of infobars for current tab (accessed via infobar helper of
506 // the current tab).
507 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
508
509 // Bridge class to deliver container change notifications to BVC.
510 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
511
512 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37513 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27514 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13515
516 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11517 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13518
sdefresnee65fd872016-12-19 13:38:13519 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27520 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13521
522 // Bridge to register for bookmark changes.
523 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
524
525 // Cached pointer to the bookmarks model.
526 bookmarks::BookmarkModel* _bookmarkModel; // weak
527
528 // The controller that shows the bookmarking UI after the user taps the star
529 // button.
stkhapuginc9eee7b2017-04-10 15:49:27530 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13531
sdefresnee65fd872016-12-19 13:38:13532 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27533 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13534
Eugene But56efc322017-08-11 14:03:44535 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28536 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13537
538 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27539 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13540
Jean-François Geyelin3d47c212017-08-03 09:24:09541 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27542 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13543
Rohit Rao01e0e002017-08-14 20:49:43544 // Coordinator for the share menu (Activity Services).
545 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
546
sdefresnee65fd872016-12-19 13:38:13547 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27548 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38549
Rohit Raocda0a992017-08-16 15:37:11550 // Coordinator for the QR scanner.
551 QRScannerLegacyCoordinator* _qrScannerCoordinator;
552
sczsdd860eba2017-08-10 01:55:38553 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13554 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53555
556 // Coordinator for displaying Sad Tab.
557 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27558
559 // Coordinator for Page Info UI.
560 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59561
562 // Coordinator for displaying Repost Form dialog.
563 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22564
edchin7f210cd2017-09-28 08:03:53565 // Coordinator for displaying snackbars.
566 SnackbarCoordinator* _snackbarCoordinator;
567
sczsf1620e52017-10-02 22:54:46568 // Coordinator for the toolbar.
569 LegacyToolbarCoordinator* _toolbarCoordinator;
570
Louis Romerod11747a2017-10-20 20:10:35571 // Coordinator for the External Search UI.
572 ExternalSearchCoordinator* _externalSearchCoordinator;
573
Justin Cohenb3170c32017-09-19 01:55:22574 // Fake status bar view used to blend the toolbar into the status bar.
575 UIView* _fakeStatusBarView;
sdefresnee65fd872016-12-19 13:38:13576}
577
578// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17579@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13580// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17581@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13582// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17583@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13584// Whether the current tab can enable the request desktop menu item.
585@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
586// Whether the sharing menu should be enabled.
587@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
588// Helper method to check web controller canShowFindBar method.
589@property(nonatomic, assign, readonly) BOOL canShowFindBar;
590// Whether the controller's view is currently available.
591// YES from viewWillAppear to viewWillDisappear.
592@property(nonatomic, assign, getter=isVisible) BOOL visible;
593// Whether the controller's view is currently visible.
594// YES from viewDidAppear to viewWillDisappear.
595@property(nonatomic, assign) BOOL viewVisible;
596// Whether the controller is currently dismissing a presented view controller.
597@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
598// Returns YES if the toolbar has not been scrolled out by fullscreen.
599@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
600 BOOL toolbarOnScreen;
601// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12602@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13603// Whether BVC prefers to hide the status bar. This value is used to determine
604// the response from the |prefersStatusBarHidden| method.
605@property(nonatomic, assign) BOOL hideStatusBar;
606// Whether the VoiceSearchBar should be displayed.
607@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
608// Coordinator for displaying a modal overlay with activity indicator to prevent
609// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17610@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13611 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39612// A block to be run when the |tabWasAdded:| method completes the animation
613// for the presentation of a new tab. Can be used to record performance metrics.
614@property(nonatomic, strong, nullable)
615 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42616// Coordinator for Recent Tabs.
617@property(nonatomic, strong)
618 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03619// Coordinator for tablet tab strip.
620@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
621// A weak reference to the view of the tab strip on tablet.
622@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13623
liaoyukeea9f3ee62017-03-07 22:05:39624// The user agent type used to load the currently visible page. User agent type
625// is NONE if there is no visible page or visible page is a native page.
626@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
627
stkhapugin952ecef2017-04-11 12:11:45628// Returns the header views, all the chrome on top of the page, including the
629// ones that cannot be scrolled off screen by full screen.
630@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
631
Cooper Knaakd0a974cd2017-08-10 18:05:47632// Used to display the new tab tip in-product help promotion bubble. |nil| if
633// the new tab tip bubble has not yet been presented. Once the bubble is
634// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38635@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47636 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38637
Helen Yang9175bd52017-08-12 00:28:40638// Used to display the new incognito tab tip in-product help promotion bubble.
639@property(nonatomic, strong)
640 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
641
sdefresnee65fd872016-12-19 13:38:13642// BVC initialization:
643// If the BVC is initialized with a valid browser state & tab model immediately,
644// the path is straightforward: functionality is enabled, and the UI is built
645// when -viewDidLoad is called.
646// If the BVC is initialized without a browser state or tab model, the tab model
647// and browser state may or may not be provided before -viewDidLoad is called.
648// In most cases, they will not, to improve startup performance.
649// In order to handle this, initialization of various aspects of BVC have been
650// broken out into the following functions, which have expectations (enforced
651// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
652
653// Registers for notifications.
654- (void)registerForNotifications;
655// Called when a tab is starting to load. If it's a link click or form
656// submission, the user is navigating away from any entries in the forward
657// history. Tell the toolbar so it can update the UI appropriately.
658// See the warning on [Tab webWillStartLoadingURL] about invocation of this
659// method sequence by malicious pages.
660- (void)pageLoadStarting:(NSNotification*)notify;
661// Called when a tab actually starts loading.
662- (void)pageLoadStarted:(NSNotification*)notify;
663// Called when a tab finishes loading. Update the Omnibox with the url and
664// stop any page load progess display.
665- (void)pageLoadComplete:(NSNotification*)notify;
666// Called when a tab is deselected in the model.
667// This notification also occurs when a tab is closed.
668- (void)tabDeselected:(NSNotification*)notify;
669// Animates sliding current tab and rotate-entering new tab while new tab loads
670// in background on the iPhone only.
671- (void)tabWasAdded:(NSNotification*)notify;
672
673// Updates non-view-related functionality with the given browser state and tab
674// model.
675// Does not matter whether or not the view has been loaded.
676- (void)updateWithTabModel:(TabModel*)model
677 browserState:(ios::ChromeBrowserState*)browserState;
678// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
679// the status bar to mimic this layout.
680- (void)installFakeStatusBar;
681// Builds the UI parts of tab strip and the toolbar. Does not matter whether
682// or not browser state and tab model are valid.
683- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50684// Sets up the constraints on the toolbar.
685- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13686// Updates view-related functionality with the given tab model and browser
687// state. The view must have been loaded. Uses |_browserState| and |_model|.
688- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28689// Sets the correct frame and hierarchy for subviews and helper views. Only
690// insert views on |initialLayout|.
691- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13692// Makes |tab| the currently visible tab, displaying its view. Calls
693// -selectedTabChanged on the toolbar only if |newSelection| is YES.
694- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
695// Initializes the bookmark interaction controller if not already initialized.
696- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13697// Add all delegates to the provided |tab|.
698- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14699// Remove delegates from the provided |tab|.
700- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13701// Closes the current tab, with animation if applicable.
702- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13703// Show the bookmarks page.
704- (void)showAllBookmarks;
705// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23706- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13707// Dismisses the "rate this app" dialog.
708- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32709// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13710- (BOOL)isTabNativePage:(Tab*)tab;
711// Returns the view to use when animating a page in or out, positioning it to
712// fill the content area but not actually adding it to the view hierarchy.
713- (UIImageView*)pageOpenCloseAnimationView;
714// Returns the view to use when animating full screen NTP paper in, filling the
715// entire screen but not actually adding it to the view hierarchy.
716- (UIImageView*)pageFullScreenOpenCloseAnimationView;
717// Updates the toolbar display based on the current tab.
718- (void)updateToolbar;
719// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12720// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13721- (void)updateDialogPresenterActiveState;
722// Dismisses popups and modal dialogs that are displayed above the BVC upon size
723// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
724// is performed.
725// TODO(crbug.com/522721): Support size changes for all popups and modal
726// dialogs.
727- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47728
729// Returns a bubble associated with an in-product help promotion if
730// it is valid to show the promotion and |nil| otherwise. |feature| is the
731// base::Feature object associated with the given promotion. |direction| is the
732// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21733// arrow on the button. |text| is the text displayed by the bubble. This method
734// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47735- (BubbleViewControllerPresenter*)
736bubblePresenterForFeature:(const base::Feature&)feature
737 direction:(BubbleArrowDirection)direction
738 alignment:(BubbleAlignment)alignment
739 text:(NSString*)text;
740
Cooper Knaak120cee5e2017-08-10 20:57:00741// Waits to present a bubble associated with the new tab tip in-product help
742// promotion until the feature engagement tracker database is fully initialized.
743// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
744// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11745// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21746// determines it is not valid to present it. This method requires that
747// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00748- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11749// Optionally presents a bubble associated with the new tab tip in-product help
750// promotion. If the feature engagement tracker determines it is valid to show
751// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
752// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21753// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
754// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00755- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40756// Waits to present a bubble associated with the new incognito tab tip
757// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21758// fully initialized. This method requires that |self.browserState| is
759// not NULL.
Helen Yang9175bd52017-08-12 00:28:40760- (void)presentNewIncognitoTabTipBubbleOnInitialized;
761// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21762// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40763- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21764// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
765// eligible. Only one can be eligible per session (as enforced by the
766// FeatureEngagementTracker). If neither is eligible, neither bubble is
767// presented. This method requires that |self.browserState| is not NULL.
768- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00769
sdefresnee65fd872016-12-19 13:38:13770// Update find bar with model data. If |shouldFocus| is set to YES, the text
771// field will become first responder.
772- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13773// Hide find bar.
774- (void)hideFindBarWithAnimation:(BOOL)animate;
775// Shows find bar. If |selectText| is YES, all text inside the Find Bar
776// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
777// set to be first responder.
778- (void)showFindBarWithAnimation:(BOOL)animate
779 selectText:(BOOL)selectText
780 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36781
sdefresnee65fd872016-12-19 13:38:13782// The infobar state (typically height) has changed.
783- (void)infoBarContainerStateChanged:(bool)is_animating;
784// Adds a CardView on top of the contentArea either taking the size of the full
785// screen or just the size of the space under the header.
786// Returns the CardView that was added.
787- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
788// Called when either a tab finishes loading or when a tab with finished content
789// is added directly to the model via pre-rendering. The tab must be non-nil and
790// must be a member of the tab model controlled by this BrowserViewController.
791- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
792// Evaluates Javascript asynchronously using the current page context.
793- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36794// Shows a self-dismissing snackbar displaying |message|.
795- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13796// Induces an intentional crash in the browser process.
797- (void)induceBrowserCrash;
798// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17799- (void)managePermissionAndSaveImage:(NSData*)data
800 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13801// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24802// saved as a temporary file on disk then saved in photos. Saving will happen
803// on a background sequence and the completion block will be invoked on that
804// sequence.
805- (void)saveImage:(NSData*)data
806 withFileExtension:(NSString*)fileExtension
807 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13808// Called when Chrome has been denied access to the photos or videos and the
809// user can change it.
810// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
811// settings. Dismiss previous alert if it has not been dismissed yet.
812- (void)displayImageErrorAlertWithSettingsOnMainQueue;
813// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
814// previous alert if it has not been dismissed yet.
815- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
816// Called when Chrome has been denied access to the photos or videos and the
817// user cannot change it.
818// Shows a privacy alert on the main queue, with errorContent as the message.
819// Dismisses previous alert if it has not been dismissed yet.
820- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
821// Called with the results of saving a picture in the photo album. If error is
822// nil the save succeeded.
823- (void)finishSavingImageWithError:(NSError*)error;
824// Provides a view that encompasses currently displayed infobar(s) or nil
825// if no infobar is presented.
826- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
827// Returns a vertical infobar offset relative to the tab content.
828- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
829// Provides a view that encompasses the voice search bar if it's displayed or
830// nil if the voice search bar isn't displayed.
831- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
832// Returns a vertical voice search bar offset relative to the tab content.
833- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
834// Lazily instantiates |_voiceSearchController|.
835- (void)ensureVoiceSearchControllerCreated;
836// Lazily instantiates |_voiceSearchBar| and adds it to the view.
837- (void)ensureVoiceSearchBarCreated;
838// Shows/hides the voice search bar.
839- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
840// The LogoAnimationControllerOwner to be used for the next logo transition
841// animation.
842- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13843// Returns the footer view if one exists (e.g. the voice search bar).
844- (UIView*)footerView;
845// Returns the height of the header view for the tab model's current tab.
846- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13847// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45848- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13849 atOffset:(CGFloat)headerOffset;
850// Returns the y coordinate for the footer's frame when animating the footer
851// in/out of fullscreen.
852- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
853// Called when the animation for setting the header view's offset is finished.
854// |completed| should indicate if the animation finished completely or was
855// interrupted. |offset| should indicate the header offset after the animation.
856// |dragged| should indicate if the header moved due to the user dragging.
Kurt Horimoto62e97c72017-11-03 19:51:47857- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:13858 headerAnimationCompleted:(BOOL)completed
859 offset:(CGFloat)offset;
860// Performs a search with the image at the given url. The referrer is used to
861// download the image.
862- (void)searchByImageAtURL:(const GURL&)url
863 referrer:(const web::Referrer)referrer;
864// Saves the image at the given URL on the system's album. The referrer is used
865// to download the image.
866- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
867
Mark Cogandfcdea72017-07-18 13:47:38868// Record the last tap point based on the |originPoint| (if any) passed in
869// |command|.
870- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13871// Get return the last stored |_lastTapPoint| if it's been set within the past
872// second.
873- (CGPoint)lastTapPoint;
874// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
875- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
876// Returns the native controller being used by |tab|'s web controller.
877- (id)nativeControllerForTab:(Tab*)tab;
878// Installs the BVC as overscroll actions controller of |nativeContent| if
879// needed. Sets the style of the overscroll actions toolbar.
880- (void)setOverScrollActionControllerToStaticNativeContent:
881 (StaticHtmlNativeContent*)nativeContent;
882// Whether the BVC should declare keyboard commands.
883- (BOOL)shouldRegisterKeyboardCommands;
884// Adds the given url to the reading list.
885- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
886@end
887
888class InfoBarContainerDelegateIOS
889 : public infobars::InfoBarContainer::Delegate {
890 public:
891 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
892 : controller_(controller) {}
893
894 ~InfoBarContainerDelegateIOS() override {}
895
896 private:
897 SkColor GetInfoBarSeparatorColor() const override {
898 NOTIMPLEMENTED();
899 return SK_ColorBLACK;
900 }
901
902 int ArrowTargetHeightForInfoBar(
903 size_t index,
904 const gfx::SlideAnimation& animation) const override {
905 return 0;
906 }
907
908 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
909 int arrow_target_height,
910 int bar_target_height,
911 int* arrow_height,
912 int* arrow_half_width,
913 int* bar_height) const override {
914 DCHECK_NE(-1, bar_target_height)
915 << "Infobars don't have a default height on iOS";
916 *arrow_height = 0;
917 *arrow_half_width = 0;
918 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
919 }
920
921 void InfoBarContainerStateChanged(bool is_animating) override {
922 [controller_ infoBarContainerStateChanged:is_animating];
923 }
924
925 bool DrawInfoBarArrows(int* x) const override { return false; }
926
stkhapuginf58b10d02017-04-10 13:36:17927 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13928};
929
930// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
931@interface BrowserViewController (BookmarkBridgeMethods)
932// If a bookmark matching the currentTab url is added or moved, update the
933// toolbar state so the star highlight is in sync.
934- (void)bookmarkNodeModified:(const BookmarkNode*)node;
935- (void)allBookmarksRemoved;
936@end
937
938// Handle notification that bookmarks has been removed changed so we can update
939// the bookmarked star icon.
940class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
941 public:
942 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
943 : owner_(owner) {}
944
945 ~BrowserBookmarkModelBridge() override {}
946
947 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
948 const BookmarkNode* parent,
949 int old_index,
950 const BookmarkNode* node,
951 const std::set<GURL>& removed_urls) override {
952 [owner_ bookmarkNodeModified:node];
953 }
954
955 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
956 bool ids_reassigned) override {}
957
958 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
959 const BookmarkNode* old_parent,
960 int old_index,
961 const BookmarkNode* new_parent,
962 int new_index) override {}
963
964 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
965 const BookmarkNode* parent,
966 int index) override {
967 [owner_ bookmarkNodeModified:parent->GetChild(index)];
968 }
969
970 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
971 const BookmarkNode* node) override {}
972
973 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
974 const BookmarkNode* node) override {}
975
976 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
977 const BookmarkNode* node) override {}
978
979 void BookmarkAllUserNodesRemoved(
980 bookmarks::BookmarkModel* model,
981 const std::set<GURL>& removed_urls) override {
982 [owner_ allBookmarksRemoved];
983 }
984
985 private:
stkhapuginf58b10d02017-04-10 13:36:17986 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13987};
988
989@implementation BrowserViewController
990
991@synthesize contentArea = _contentArea;
992@synthesize typingShield = _typingShield;
993@synthesize active = _active;
994@synthesize visible = _visible;
995@synthesize viewVisible = _viewVisible;
996@synthesize dismissingModal = _dismissingModal;
997@synthesize hideStatusBar = _hideStatusBar;
998@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
999@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:391000@synthesize foregroundTabWasAddedCompletionBlock =
1001 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:401002@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
1003@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:421004@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:031005@synthesize tabStripCoordinator = _tabStripCoordinator;
1006@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:131007
1008#pragma mark - Object lifecycle
1009
Mark Cogan5e3da152017-07-11 15:57:301010- (instancetype)
1011 initWithTabModel:(TabModel*)model
1012 browserState:(ios::ChromeBrowserState*)browserState
1013 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
1014applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:131015 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
1016 if (self) {
1017 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:171018
stkhapuginc9eee7b2017-04-10 15:49:271019 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:271020 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
1021 presentingViewController:self];
justincohen75011c32017-04-28 16:31:391022 _dispatcher = [[CommandDispatcher alloc] init];
1023 [_dispatcher startDispatchingToTarget:self
1024 forProtocol:@protocol(UrlLoader)];
1025 [_dispatcher startDispatchingToTarget:self
1026 forProtocol:@protocol(WebToolbarDelegate)];
1027 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241028 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301029 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1030 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321031 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1032 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1033 // dispatched to the endpoint as well. Since this is potentially
1034 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1035 DCHECK(!applicationCommandEndpoint ||
1036 [applicationCommandEndpoint
1037 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1038 [_dispatcher
1039 startDispatchingToTarget:applicationCommandEndpoint
1040 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391041
edchin7f210cd2017-09-28 08:03:531042 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
1043 _snackbarCoordinator.dispatcher = _dispatcher;
1044 [_snackbarCoordinator start];
1045
sdefresnee65fd872016-12-19 13:38:131046 _javaScriptDialogPresenter.reset(
1047 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1048 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1049 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151050 [[UpgradeCenter sharedInstance] registerClient:self
1051 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131052 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131053 if (model && browserState)
1054 [self updateWithTabModel:model browserState:browserState];
1055 if ([[NSUserDefaults standardUserDefaults]
1056 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271057 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131058 }
1059 }
1060 return self;
1061}
1062
1063- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1064 bundle:(NSBundle*)nibBundleOrNil {
1065 NOTREACHED();
1066 return nil;
1067}
1068
1069- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1070 NOTREACHED();
1071 return nil;
1072}
1073
1074- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201075 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131076}
1077
1078#pragma mark - Accessibility
1079
1080- (BOOL)accessibilityPerformEscape {
1081 [self dismissPopups];
1082 return YES;
1083}
1084
1085#pragma mark - Properties
1086
edchin3365c7d2017-09-01 22:20:371087- (id<ApplicationCommands,
1088 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371089 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531090 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371091 UrlLoader,
1092 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561093 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531094 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1095 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241096}
1097
sdefresnee65fd872016-12-19 13:38:131098- (void)setActive:(BOOL)active {
1099 if (_active == active) {
1100 return;
1101 }
1102 _active = active;
1103
1104 // If not active, display an activity indicator overlay over the view to
1105 // prevent interaction with the web page.
1106 // TODO(crbug.com/637093): This coordinator should be managed by the
1107 // coordinator used to present BrowserViewController, when implemented.
1108 if (active) {
1109 [self.activityOverlayCoordinator stop];
1110 self.activityOverlayCoordinator = nil;
1111 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171112 self.activityOverlayCoordinator =
1113 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131114 [self.activityOverlayCoordinator start];
1115 }
1116
1117 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091118 ActiveStateManager* active_state_manager =
1119 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131120 active_state_manager->SetActive(active);
1121 }
1122
1123 [_model setWebUsageEnabled:active];
1124 [self updateDialogPresenterActiveState];
1125
1126 if (active) {
1127 // Make sure the tab (if any; it's possible to get here without a current
1128 // tab if the caller is about to create one) ends up on screen completely.
1129 Tab* currentTab = [_model currentTab];
1130 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441131 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131132 if (_expectingForegroundTab)
1133 [currentTab.webController setOverlayPreviewMode:YES];
1134 if (currentTab)
1135 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341136 } else {
1137 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131138 }
sdefresnee65fd872016-12-19 13:38:131139 [_paymentRequestManager enablePaymentRequest:active];
1140
1141 [self setNeedsStatusBarAppearanceUpdate];
1142}
1143
1144- (void)setPrimary:(BOOL)primary {
1145 [_model setPrimary:primary];
1146 if (primary) {
1147 [self updateDialogPresenterActiveState];
1148 } else {
1149 self.dialogPresenter.active = false;
1150 }
1151}
1152
1153- (BOOL)isPlayingTTS {
1154 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1155}
1156
sdefresne6165c8742017-01-16 15:42:021157- (ios::ChromeBrowserState*)browserState {
1158 return _browserState;
1159}
1160
1161- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271162 return _model;
sdefresne6165c8742017-01-16 15:42:021163}
1164
sdefresnee65fd872016-12-19 13:38:131165- (SideSwipeController*)sideSwipeController {
1166 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271167 _sideSwipeController =
1168 [[SideSwipeController alloc] initWithTabModel:_model
1169 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131170 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311171 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131172 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031173 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131174 }
1175 return _sideSwipeController;
1176}
1177
sdefresnee65fd872016-12-19 13:38:131178- (DialogPresenter*)dialogPresenter {
1179 return _dialogPresenter;
1180}
1181
sdefresnee65fd872016-12-19 13:38:131182- (BOOL)canUseDesktopUserAgent {
1183 Tab* tab = [_model currentTab];
1184 if ([self isTabNativePage:tab])
1185 return NO;
1186
1187 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441188 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131189}
1190
1191// Whether the sharing menu should be shown.
1192- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191193 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251194 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131195}
1196
1197- (BOOL)canShowFindBar {
1198 // Make sure web controller can handle find in page.
1199 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421200 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131201 return NO;
rohitrao005a6432017-03-16 20:52:421202 }
sdefresnee65fd872016-12-19 13:38:131203
rohitrao005a6432017-03-16 20:52:421204 auto* helper = FindTabHelper::FromWebState(tab.webState);
1205 return (helper && helper->CurrentPageSupportsFindInPage() &&
1206 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131207}
1208
liaoyukeea9f3ee62017-03-07 22:05:391209- (web::UserAgentType)userAgentType {
1210 web::WebState* webState = [_model currentTab].webState;
1211 if (!webState)
1212 return web::UserAgentType::NONE;
1213 web::NavigationItem* visibleItem =
1214 webState->GetNavigationManager()->GetVisibleItem();
1215 if (!visibleItem)
1216 return web::UserAgentType::NONE;
1217
1218 return visibleItem->GetUserAgentType();
1219}
1220
sdefresnee65fd872016-12-19 13:38:131221- (void)setVisible:(BOOL)visible {
1222 if (_visible == visible)
1223 return;
1224 _visible = visible;
1225}
1226
1227- (void)setViewVisible:(BOOL)viewVisible {
1228 if (_viewVisible == viewVisible)
1229 return;
1230 _viewVisible = viewVisible;
1231 self.visible = viewVisible;
1232 [self updateDialogPresenterActiveState];
1233}
1234
1235- (BOOL)isToolbarOnScreen {
1236 return [self headerHeight] - [self currentHeaderOffset] > 0;
1237}
1238
kkhorimotoa44349c12017-04-12 23:02:121239- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1240 if (_inNewTabAnimation == inNewTabAnimation)
1241 return;
1242 _inNewTabAnimation = inNewTabAnimation;
1243 [self updateDialogPresenterActiveState];
1244}
1245
sdefresnee65fd872016-12-19 13:38:131246- (BOOL)isInNewTabAnimation {
1247 return _inNewTabAnimation;
1248}
1249
1250- (BOOL)shouldShowVoiceSearchBar {
1251 // On iPads, the voice search bar should only be shown for regular horizontal
1252 // size class configurations. It should always be shown for voice search
1253 // results Tabs on iPhones, including configurations with regular horizontal
1254 // size classes (i.e. landscape iPhone 6 Plus).
1255 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1256 UIUserInterfaceSizeClassCompact;
1257 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1258 (!IsIPadIdiom() || compactWidth);
1259}
1260
1261- (void)setHideStatusBar:(BOOL)hideStatusBar {
1262 if (_hideStatusBar == hideStatusBar)
1263 return;
1264 _hideStatusBar = hideStatusBar;
1265 [self setNeedsStatusBarAppearanceUpdate];
1266}
1267
1268#pragma mark - IBActions
1269
1270- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461271 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131272}
1273
Cooper Knaakd0a974cd2017-08-10 18:05:471274- (void)userEnteredTabSwitcher {
1275 if ([self.tabTipBubblePresenter isUserEngaged]) {
1276 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1277 }
1278}
1279
Cooper Knaake963d6702017-08-11 21:03:111280- (void)presentBubblesIfEligible {
1281 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401282 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111283}
1284
sdefresnee65fd872016-12-19 13:38:131285#pragma mark - UIViewController methods
1286
1287// Perform additional set up after loading the view, typically from a nib.
1288- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091289 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281290 initialViewsRect.origin.y += StatusBarHeight();
1291 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131292 UIViewAutoresizing initialViewAutoresizing =
1293 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1294
stkhapuginf58b10d02017-04-10 13:36:171295 self.contentArea =
1296 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131297 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171298 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131299 self.typingShield.autoresizingMask = initialViewAutoresizing;
1300 [self.typingShield addTarget:self
1301 action:@selector(shieldWasTapped:)
1302 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131303 self.view.autoresizingMask = initialViewAutoresizing;
1304 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1305 [self.view addSubview:self.contentArea];
1306 [self.view addSubview:self.typingShield];
1307 [super viewDidLoad];
1308
1309 // Install fake status bar for iPad iOS7
1310 [self installFakeStatusBar];
1311 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281312 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451313 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501314 [self addConstraintsToToolbar];
1315 }
sdefresnee65fd872016-12-19 13:38:131316 // If the tab model and browser state are valid, finish initialization.
1317 if (_model && _browserState)
1318 [self addUIFunctionalityForModelAndBrowserState];
1319
1320 // Add a tap gesture recognizer to save the last tap location for the source
1321 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271322 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1323 initWithTarget:self
1324 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131325 [tapRecognizer setDelegate:self];
1326 [tapRecognizer setCancelsTouchesInView:NO];
1327 [_contentArea addGestureRecognizer:tapRecognizer];
1328}
1329
Justin Cohenb3170c32017-09-19 01:55:221330- (void)viewSafeAreaInsetsDidChange {
1331 [super viewSafeAreaInsetsDidChange];
1332 // Gate this behind iPhone X, since it's currently the only device that
1333 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501334 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281335 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501336 }
Justin Cohenba27610e2017-11-08 19:34:451337 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271338 // TODO(crbug.com/778236): Check if this call can be removed once the
1339 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271340 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271341 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501342 }
Justin Cohenb3170c32017-09-19 01:55:221343}
1344
sdefresnee65fd872016-12-19 13:38:131345- (void)viewDidAppear:(BOOL)animated {
1346 [super viewDidAppear:animated];
1347 self.viewVisible = YES;
1348 [self updateDialogPresenterActiveState];
Gregory Chatzinoff541b8642017-10-25 00:25:211349
1350 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1351 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1352 // check for |self.browserState| before calling the presenting the bubbles.
1353 if (self.browserState) {
1354 [self presentBubblesIfEligible];
1355 }
sdefresnee65fd872016-12-19 13:38:131356}
1357
1358- (void)viewWillAppear:(BOOL)animated {
1359 [super viewWillAppear:animated];
1360
Rohit Rao9a8ad772017-10-30 22:35:591361 // Reparent the toolbar if it's been relinquished. If the tab switcher
1362 // presentation experiment is enabled, only do this if the parent VC is not
1363 // currently being presented. Otherwise, reparenting here would remove the
1364 // toolbar from the tab switcher while the switcher is in the process of
1365 // animating.
1366 if (_isToolbarControllerRelinquished) {
1367 if (!TabSwitcherPresentsBVCEnabled() ||
1368 (!self.beingPresented && !self.parentViewController.beingPresented)) {
1369 [self reparentToolbarController];
1370 }
1371 }
sdefresnee65fd872016-12-19 13:38:131372
1373 self.visible = YES;
1374
1375 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521376 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131377 _infoBarContainer->RestoreInfobars();
1378 }
1379
1380 // If the controller is suspended, or has been paged out due to low memory,
1381 // updating the view will be handled when it's displayed again.
1382 if (![_model webUsageEnabled] || !self.contentArea)
1383 return;
1384 // Update the displayed tab (if any; the switcher may not have created one
1385 // yet) in case it changed while showing the switcher.
1386 Tab* currentTab = [_model currentTab];
1387 if (currentTab)
1388 [self displayTab:currentTab isNewSelection:YES];
1389}
1390
1391- (void)viewWillDisappear:(BOOL)animated {
1392 self.viewVisible = NO;
1393 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131394 [[_model currentTab] wasHidden];
1395 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521396 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131397 _infoBarContainer->SuspendInfobars();
1398 }
1399 [super viewWillDisappear:animated];
1400}
1401
1402- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1403 duration:(NSTimeInterval)duration {
1404 [super willRotateToInterfaceOrientation:orient duration:duration];
1405 [self dismissPopups];
1406 [self reshowFindBarIfNeededWithCoordinator:nil];
1407}
1408
1409- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1410 [super didRotateFromInterfaceOrientation:orient];
1411
1412 // This reinitializes the toolbar, including updating the Overlay View,
1413 // if there is one.
1414 [self updateToolbar];
1415 [self infoBarContainerStateChanged:false];
1416}
1417
1418- (BOOL)prefersStatusBarHidden {
1419 return self.hideStatusBar;
1420}
1421
1422// Called when in the foreground and the OS needs more memory. Release as much
1423// as possible.
1424- (void)didReceiveMemoryWarning {
1425 // Releases the view if it doesn't have a superview.
1426 [super didReceiveMemoryWarning];
1427
1428 // Release any cached data, images, etc that aren't in use.
1429 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1430 // but since the FaviconCache uses obj-c in the header, it can't be included
1431 // there.
1432 if (_browserState) {
1433 FaviconLoader* loader =
1434 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1435 _browserState);
1436 if (loader)
1437 loader->PurgeCache();
1438 }
1439
1440 if (![self isViewLoaded]) {
1441 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1442 // as the BrowserViewController.
1443 self.contentArea = nil;
1444 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271445 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131446 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271447 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421448 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461449 _toolbarCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271450 _toolbarModelDelegate = nil;
1451 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031452 [self.tabStripCoordinator stop];
1453 self.tabStripCoordinator = nil;
1454 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271455 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131456 }
1457}
1458
1459- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1460 [super traitCollectionDidChange:previousTraitCollection];
1461 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1462 // because in some cases the presented view controller isn't a child of the
1463 // BVC in the view controller hierarchy (some intervening object isn't a
1464 // view controller).
1465 [self.presentedViewController
1466 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131467 // Update voice search bar visibility.
1468 [self updateVoiceSearchBarVisibilityAnimated:NO];
1469}
1470
1471- (void)viewWillTransitionToSize:(CGSize)size
1472 withTransitionCoordinator:
1473 (id<UIViewControllerTransitionCoordinator>)coordinator {
1474 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1475 [self dismissPopups];
1476 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1477}
1478
1479- (void)reshowFindBarIfNeededWithCoordinator:
1480 (id<UIViewControllerTransitionCoordinator>)coordinator {
1481 if (![_findBarController isFindInPageShown])
1482 return;
1483
1484 // Record focused state.
1485 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1486
1487 [self hideFindBarWithAnimation:NO];
1488
stkhapuginc9eee7b2017-04-10 15:49:271489 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131490 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1491 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271492 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131493 if (strongSelf)
1494 [strongSelf showFindBarWithAnimation:NO
1495 selectText:NO
1496 shouldFocus:isFocusedBeforeReshow];
1497 };
1498
1499 BOOL enqueued =
1500 [coordinator animateAlongsideTransition:nil completion:completion];
1501 if (!enqueued) {
1502 completion(nil);
1503 }
1504}
1505
1506- (void)dismissViewControllerAnimated:(BOOL)flag
1507 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111508 // It is an error to call this method when no VC is being presented.
1509 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1510
Rohit Raoa668c022017-11-08 00:04:441511 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1512 // multiple times. When the BVC is displayed using VC containment, multiple
1513 // calls are effectively idempotent because only the first call has any effect
1514 // and subsequent calls do nothing. However, when the BVC is presented,
1515 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111516 // want, so check for this case and return early. It is not enough to check
1517 // |self.dismissingModal| because some dismissals do not go through
1518 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441519 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111520 if (TabSwitcherPresentsBVCEnabled() &&
1521 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441522 return;
1523 }
1524
sdefresnee65fd872016-12-19 13:38:131525 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271526 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131527 [super dismissViewControllerAnimated:flag
1528 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271529 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131530 [strongSelf setDismissingModal:NO];
1531 [strongSelf setPresenting:NO];
1532 if (completion)
1533 completion();
1534 [[strongSelf dialogPresenter] tryToPresent];
1535 }];
1536}
1537
1538- (void)presentViewController:(UIViewController*)viewControllerToPresent
1539 animated:(BOOL)flag
1540 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271541 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131542 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1543 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1544 // presented, to show a temporary view of the launch screen and then remove it
1545 // when the controller for the FRE has been presented. This fix should be
1546 // removed when the FRE startup code is rewritten.
1547 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1548 experimental_flags::AlwaysDisplayFirstRun()) &&
1549 !tests_hook::DisableFirstRun();
1550 // These if statements check that |presentViewController| is being called for
1551 // the FRE case.
1552 if (firstRunLaunch &&
1553 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1554 UINavigationController* navController =
1555 base::mac::ObjCCastStrict<UINavigationController>(
1556 viewControllerToPresent);
1557 if ([navController.topViewController
1558 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1559 self.hideStatusBar = YES;
1560
1561 // Load view from Launch Screen and add it to window.
1562 NSBundle* mainBundle = base::mac::FrameworkBundle();
1563 NSArray* topObjects =
1564 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1565 UIViewController* launchScreenController =
1566 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1567 // |launchScreenView| is loaded as an autoreleased object, and is retained
1568 // by the |completion| block below.
1569 UIView* launchScreenView = launchScreenController.view;
1570 launchScreenView.userInteractionEnabled = NO;
1571 launchScreenView.frame = self.view.window.bounds;
1572 [self.view.window addSubview:launchScreenView];
1573
1574 // Replace the completion handler sent to the superclass with one which
1575 // removes |launchScreenView| and resets the status bar. If |completion|
1576 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271577 __weak BrowserViewController* weakSelf = self;
1578 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131579 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271580 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131581 if (completion)
1582 completion();
stkhapuginc9eee7b2017-04-10 15:49:271583 };
sdefresnee65fd872016-12-19 13:38:131584 }
1585 }
1586
1587 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171588 if ([_sideSwipeController inSwipe]) {
1589 [_sideSwipeController resetContentView];
1590 }
sdefresnee65fd872016-12-19 13:38:131591
1592 [super presentViewController:viewControllerToPresent
1593 animated:flag
1594 completion:finalCompletionHandler];
1595}
1596
1597#pragma mark - Notification handling
1598
1599- (void)registerForNotifications {
1600 DCHECK(_model);
1601 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1602 [defaultCenter addObserver:self
1603 selector:@selector(pageLoadStarting:)
1604 name:kTabModelTabWillStartLoadingNotification
1605 object:_model];
1606 [defaultCenter addObserver:self
1607 selector:@selector(pageLoadStarted:)
1608 name:kTabModelTabDidStartLoadingNotification
1609 object:_model];
1610 [defaultCenter addObserver:self
1611 selector:@selector(pageLoadComplete:)
1612 name:kTabModelTabDidFinishLoadingNotification
1613 object:_model];
1614 [defaultCenter addObserver:self
1615 selector:@selector(tabDeselected:)
1616 name:kTabModelTabDeselectedNotification
1617 object:_model];
1618 [defaultCenter addObserver:self
1619 selector:@selector(tabWasAdded:)
1620 name:kTabModelNewTabWillOpenNotification
1621 object:_model];
1622}
1623
1624- (void)pageLoadStarting:(NSNotification*)notify {
1625 Tab* tab = notify.userInfo[kTabModelTabKey];
1626 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511627
1628 // Stop any Find in Page searches and close the find bar when navigating to a
1629 // new page.
1630 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361631
sdefresnee65fd872016-12-19 13:38:131632 if (tab == [_model currentTab]) {
1633 // TODO(pinkerton): Fill in here about hiding the forward button on
1634 // navigation.
1635 }
1636}
1637
1638- (void)pageLoadStarted:(NSNotification*)notify {
1639 Tab* tab = notify.userInfo[kTabModelTabKey];
1640 DCHECK(tab);
1641 if (tab == [_model currentTab]) {
1642 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461643 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131644 }
1645 [self updateVoiceSearchBarVisibilityAnimated:NO];
1646 }
1647}
1648
1649- (void)pageLoadComplete:(NSNotification*)notify {
1650 // Update the UI, but only if the current tab.
1651 Tab* tab = notify.userInfo[kTabModelTabKey];
1652 if (tab == [_model currentTab]) {
1653 // There isn't any need to update the toolbar here. When the page finishes,
1654 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1655 }
1656
1657 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1658
1659 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1660}
1661
1662- (void)tabDeselected:(NSNotification*)notify {
1663 DCHECK(notify);
1664 Tab* tab = notify.userInfo[kTabModelTabKey];
1665 DCHECK(tab);
1666 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221667 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131668}
1669
1670- (void)tabWasAdded:(NSNotification*)notify {
1671 Tab* tab = notify.userInfo[kTabModelTabKey];
1672 DCHECK(tab);
1673
Eugene But56efc322017-08-11 14:03:441674 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131675
1676 // When adding new tabs, check what kind of reminder infobar should
1677 // be added to the new tab. Try to add only one of them.
1678 // This check is done when a new tab is added either through the Tools Menu
1679 // "New Tab" or through "New Tab" in Stack View Controller. This method
1680 // is called after a new tab has added and finished initial navigation.
1681 // If this is added earlier, the initial navigation may end up clearing
1682 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301683 web::WebState* webState = tab.webState;
1684 DCHECK(webState);
1685
1686 infobars::InfoBarManager* infoBarManager =
1687 InfoBarManagerImpl::FromWebState(webState);
1688 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131689 forTabId:[tab tabId]];
edchin9e7a1112017-11-07 18:28:031690 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
1691 self /* id<SigninPresenter> */)) {
edchin95c927072017-11-04 00:35:071692 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
sdefresnee65fd872016-12-19 13:38:131693 }
1694
1695 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501696 // phone-specific. Call the foreground tab added completion block; for
1697 // iPhones, this will get executed after the animation has finished.
1698 if (IsIPadIdiom()) {
1699 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431700 // This callback is called before webState is activated (on
1701 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1702 // callback asynchronously to be sure the activation is complete.
1703 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011704 // Test existence again as the block may have been deleted.
1705 if (self.foregroundTabWasAddedCompletionBlock) {
1706 self.foregroundTabWasAddedCompletionBlock();
1707 self.foregroundTabWasAddedCompletionBlock = nil;
1708 }
Olivier Robinc7e46242017-09-06 07:55:431709 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501710 }
sdefresnee65fd872016-12-19 13:38:131711 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501712 }
sdefresnee65fd872016-12-19 13:38:131713
1714 // Do nothing if browsing is currently suspended. The BVC will set everything
1715 // up correctly when browsing resumes.
1716 if (!self.visible || ![_model webUsageEnabled])
1717 return;
1718
1719 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1720
1721 // Block that starts voice search at the end of new Tab animation if
1722 // necessary.
1723 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1724 if (_startVoiceSearchAfterNewTabAnimation) {
1725 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001726 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131727 }
1728 };
1729
kkhorimotoa44349c12017-04-12 23:02:121730 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131731 if (!inBackground) {
1732 UIView* animationParentView = _contentArea;
1733 // Create the new page image, and load with the new tab page snapshot.
1734 CGFloat newPageOffset = 0;
1735 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191736 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1737 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131738 animationParentView = self.view;
1739 newPage = [self pageFullScreenOpenCloseAnimationView];
1740 } else {
1741 newPage = [self pageOpenCloseAnimationView];
1742 }
1743 newPageOffset = newPage.frame.origin.y;
1744
1745 [tab view].frame = _contentArea.bounds;
1746 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1747 [animationParentView addSubview:newPage];
1748 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521749 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131750 newPage, -newPageOffset,
1751 newPage.frame.size.height - newPage.image.size.height, origin,
1752 _isOffTheRecord, NULL, ^{
1753 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121754 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421755 // Use the model's currentTab here because it is possible that it can
1756 // be reset to a new value before the new Tab animation finished (e.g.
1757 // if another Tab shows a dialog via |dialogPresenter|). However, that
1758 // tab's view hasn't been displayed yet because it was in a new tab
1759 // animation.
1760 Tab* currentTab = [_model currentTab];
1761 if (currentTab) {
1762 [self tabSelected:currentTab];
1763 }
sdefresnee65fd872016-12-19 13:38:131764 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391765
1766 if (self.foregroundTabWasAddedCompletionBlock) {
1767 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451768 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391769 }
sdefresnee65fd872016-12-19 13:38:131770 });
1771 } else {
1772 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1773 // snapshot before adding the views needed for the background animation.
1774 Tab* topTab = [_model currentTab];
1775 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1776 visibleFrameOnly:self.isToolbarOnScreen];
1777 // Add three layers in order on top of the contentArea for the animation:
1778 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271779 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131780 InstallBackgroundInView(background);
1781 [_contentArea addSubview:background];
1782
1783 // 2. A CardView displaying the data from the current tab.
1784 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1785 NSString* title = [topTab title];
1786 if (![title length])
1787 title = [topTab urlDisplayString];
1788 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131789 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371790 [topCard setFavicon:nil];
1791
1792 favicon::FaviconDriver* faviconDriver =
1793 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1794 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1795 gfx::Image favicon = faviconDriver->GetFavicon();
1796 if (!favicon.IsEmpty())
1797 [topCard setFavicon:favicon.ToUIImage()];
1798 }
sdefresnee65fd872016-12-19 13:38:131799
1800 // 3. A new, blank CardView to represent the new tab being added.
1801 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521802 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131803 topCard, [_contentArea frame], IsPortrait(), ^{
1804 [background removeFromSuperview];
1805 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121806 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131807 // Resnapshot the top card if it has its own toolbar, as the toolbar
1808 // will be captured in the new tab animation, but isn't desired for
1809 // the stack view snapshots.
1810 id nativeController = [self nativeControllerForTab:topTab];
1811 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1812 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1813 startVoiceSearchIfNecessaryBlock();
1814 });
peterlaurens9f1b6e02017-06-22 17:46:451815 // Reset the foreground tab completion block so that it can never be
1816 // called more than once regardless of foreground/background tab
1817 // appearances.
1818 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131819 }
1820}
1821
1822#pragma mark - UI Configuration and Layout
1823
1824- (void)updateWithTabModel:(TabModel*)model
1825 browserState:(ios::ChromeBrowserState*)browserState {
1826 DCHECK(model);
1827 DCHECK(browserState);
1828 DCHECK(!_model);
1829 DCHECK(!_browserState);
1830 _browserState = browserState;
1831 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271832 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381833
sdefresnee65fd872016-12-19 13:38:131834 [_model addObserver:self];
1835
1836 if (!_isOffTheRecord) {
1837 [DefaultIOSWebViewFactory
1838 registerWebViewFactory:[ChromeWebViewFactory class]];
1839 }
1840 NSUInteger count = [_model count];
1841 for (NSUInteger index = 0; index < count; ++index)
1842 [self installDelegatesForTab:[_model tabAtIndex:index]];
1843
1844 [self registerForNotifications];
1845
gambardbdc07cc2017-02-03 16:43:111846 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121847 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271848 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131849
sdefresnedc432f42017-01-17 14:36:591850 // Register for bookmark changed notification (BookmarkModel may be null
1851 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131852 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591853 if (_bookmarkModel) {
1854 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1855 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1856 }
sdefresnee65fd872016-12-19 13:38:131857}
1858
sdefresnee65fd872016-12-19 13:38:131859- (void)browserStateDestroyed {
1860 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131861 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271862 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461863 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131864 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381865
1866 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431867 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111868 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381869 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271870 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351871 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031872 [self.tabStripCoordinator stop];
1873 self.tabStripCoordinator = nil;
1874 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381875
sdefresnee65fd872016-12-19 13:38:131876 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391877 [_dispatcher stopDispatchingToTarget:self];
1878 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131879}
1880
1881- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221882 CGFloat statusBarHeight = StatusBarHeight();
1883 CGRect statusBarFrame =
1884 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1885 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1886 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131887 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221888 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1889 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1890 [_fakeStatusBarView layer].zPosition = 99;
1891 [[self view] addSubview:_fakeStatusBarView];
1892 } else {
1893 // Add a white bar on phone so that the status bar on the NTP is white.
1894 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1895 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131896 }
1897}
1898
1899// Create the UI elements. May or may not have valid browser state & tab model.
1900- (void)buildToolbarAndTabStrip {
1901 DCHECK([self isViewLoaded]);
1902 DCHECK(!_toolbarModelDelegate);
1903
Rohit Rao44f204302017-08-10 14:49:541904 // Initialize the prerender service before creating the toolbar controller.
1905 PrerenderService* prerenderService =
1906 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1907 if (prerenderService) {
1908 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131909 }
1910
1911 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201912 _toolbarModelDelegate.reset(
1913 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131914 _toolbarModelIOS.reset([_dependencyFactory
1915 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
sczsf1620e52017-10-02 22:54:461916 _toolbarCoordinator =
1917 [[LegacyToolbarCoordinator alloc] initWithBaseViewController:self];
Gauthier Ambard29939db12017-10-30 16:47:311918 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sczsf1620e52017-10-02 22:54:461919 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051920 [_toolbarCoordinator
1921 setWebToolbar:[_dependencyFactory
1922 newWebToolbarControllerWithDelegate:self
1923 urlLoader:self
1924 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461925 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391926 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461927 [_toolbarCoordinator setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271928 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461929 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051930 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131931
sdefresnee65fd872016-12-19 13:38:131932 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031933 self.tabStripCoordinator =
1934 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1935 self.tabStripCoordinator.browserState = _browserState;
1936 self.tabStripCoordinator.dispatcher = _dispatcher;
1937 self.tabStripCoordinator.tabModel = _model;
1938 self.tabStripCoordinator.presentationProvider = self;
1939 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471940 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031941 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131942 }
1943
1944 // Create infobar container.
1945 if (!_infoBarContainerDelegate) {
1946 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1947 _infoBarContainer.reset(
1948 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1949 }
1950}
1951
Jean-François Geyelined4cde72017-10-11 11:34:501952- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111953 NSLayoutYAxisAnchor* topAnchor;
1954 // On iPad, the toolbar is underneath the tab strip.
1955 // On iPhone, it is underneath the top of the screen.
1956 if (IsIPadIdiom()) {
1957 topAnchor = self.tabStripView.bottomAnchor;
1958 } else {
1959 topAnchor = [self view].topAnchor;
1960 }
1961
Gauthier Ambard100670f72017-10-27 09:54:271962 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501963
1964 [NSLayoutConstraint activateConstraints:@[
sczs42f7f7482017-11-08 01:13:271965 [_toolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501966 constraintEqualToAnchor:[self view].leadingAnchor],
sczs42f7f7482017-11-08 01:13:271967 [_toolbarCoordinator.toolbarViewController.view.topAnchor
1968 constraintEqualToAnchor:topAnchor],
1969 [_toolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501970 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501971 ]];
1972 [[self view] layoutIfNeeded];
1973}
1974
sdefresnee65fd872016-12-19 13:38:131975// Enable functionality that only makes sense if the views are loaded and
1976// both browser state and tab model are valid.
1977- (void)addUIFunctionalityForModelAndBrowserState {
1978 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051979 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131980 DCHECK(_model);
1981 DCHECK([self isViewLoaded]);
1982
1983 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1984
Rohit Raoaf46af92017-08-10 12:52:301985 infobars::InfoBarManager* infoBarManager = nullptr;
1986 if (_model.currentTab) {
1987 DCHECK(_model.currentTab.webState);
1988 infoBarManager =
1989 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1990 }
sdefresnee65fd872016-12-19 13:38:131991 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1992
sczsdd860eba2017-08-10 01:55:381993 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431994 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1995 initWithBaseViewController:self];
1996 _activityServiceCoordinator.dispatcher = _dispatcher;
1997 _activityServiceCoordinator.tabModel = _model;
1998 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461999 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:052000 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:432001 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:432002
Rohit Raocda0a992017-08-16 15:37:112003 _qrScannerCoordinator =
2004 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
2005 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:052006 _qrScannerCoordinator.loadProvider =
2007 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:112008 _qrScannerCoordinator.presentationProvider = self;
2009
sczsdd860eba2017-08-10 01:55:382010 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:132011 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:382012 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:462013 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:052014 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:382015 _tabHistoryCoordinator.tabModel = _model;
2016 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462017 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:052018 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382019
sczs6ae47ad2017-09-06 17:26:532020 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202021 _sadTabCoordinator.baseViewController = self;
2022 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532023
Gregory Chatzinoffdf93d692017-09-09 01:32:272024 _pageInfoCoordinator =
2025 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
2026 _pageInfoCoordinator.browserState = _browserState;
2027 _pageInfoCoordinator.dispatcher = _dispatcher;
2028 _pageInfoCoordinator.loader = self;
2029 _pageInfoCoordinator.presentationProvider = self;
2030 _pageInfoCoordinator.tabModel = _model;
2031
Louis Romerod11747a2017-10-20 20:10:352032 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2033 _externalSearchCoordinator.dispatcher = _dispatcher;
2034
mathp9b4c11d2017-07-06 20:24:132035 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272036 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132037 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202038 browserState:_browserState
2039 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052040 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192041 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132042 }
2043}
2044
2045// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:282046- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:132047 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132048 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132049 CGFloat minY = [self headerOffset];
2050
Justin Cohenb3170c32017-09-19 01:55:222051 // Update the fake toolbar background height.
2052 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2053 fakeStatusBarFrame.size.height = StatusBarHeight();
2054 _fakeStatusBarView.frame = fakeStatusBarFrame;
2055
edchinf5150c682017-09-18 02:50:032056 if (self.tabStripView) {
2057 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132058 }
2059
2060 // Position the toolbar next, either at the top of the browser view or
2061 // directly under the tabstrip.
Justin Cohen4eeada32017-11-13 18:21:282062 if (initialLayout)
2063 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
sczs42f7f7482017-11-08 01:13:272064 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
sdefresnee65fd872016-12-19 13:38:132065 toolbarFrame.origin = CGPointMake(0, minY);
2066 toolbarFrame.size.width = widthOfView;
Justin Cohenba27610e2017-11-08 19:34:452067 if (!IsSafeAreaCompatibleToolbarEnabled()) {
sczs42f7f7482017-11-08 01:13:272068 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502069 }
sdefresnee65fd872016-12-19 13:38:132070
2071 // Place the infobar container above the content area.
2072 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282073 if (initialLayout)
2074 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132075
2076 // Place the toolbar controller above the infobar container.
Justin Cohen4eeada32017-11-13 18:21:282077 if (initialLayout)
2078 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
2079 aboveSubview:infoBarContainerView];
sdefresnee65fd872016-12-19 13:38:132080 minY += CGRectGetHeight(toolbarFrame);
Justin Cohen4eeada32017-11-13 18:21:282081 if (initialLayout)
2082 [_toolbarCoordinator.toolbarViewController
2083 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132084
2085 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2086 // content slightly.
sczs8c837782017-10-03 02:57:242087 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132088
2089 // Adjust the content area to be under the toolbar, for fullscreen or below
2090 // the toolbar is not fullscreen.
2091 CGRect contentFrame = [_contentArea frame];
2092 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222093 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2094 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132095 [_contentArea setFrame:contentFrame];
2096
2097 // Adjust the infobar container to be either at the bottom of the screen
2098 // (iPhone) or on the lower toolbar edge (iPad).
2099 CGRect infoBarFrame = contentFrame;
2100 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2101 infoBarFrame.size.height = 0;
2102 [infoBarContainerView setFrame:infoBarFrame];
2103
2104 // Attach the typing shield to the content area but have it hidden.
2105 [_typingShield setFrame:[_contentArea frame]];
Justin Cohen4eeada32017-11-13 18:21:282106 if (initialLayout)
2107 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132108 [_typingShield setHidden:YES];
2109 _typingShield.accessibilityIdentifier = @"Typing Shield";
2110 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
2111}
2112
sdefresnee65fd872016-12-19 13:38:132113- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2114 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442115 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132116
kkhorimotoa44349c12017-04-12 23:02:122117 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132118 // Hide findbar. |updateToolbar| will restore the findbar later.
2119 [self hideFindBarWithAnimation:NO];
2120
2121 // Make new content visible, resizing it first as the orientation may
2122 // have changed from the last time it was displayed.
2123 [[tab view] setFrame:_contentArea.bounds];
2124 [_contentArea displayContentView:[tab view]];
2125 }
2126 [self updateToolbar];
2127
2128 if (newSelection)
sczsf1620e52017-10-02 22:54:462129 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132130
2131 // Notify the Tab that it was displayed.
2132 [tab wasShown];
2133}
2134
2135- (void)initializeBookmarkInteractionController {
2136 if (_bookmarkInteractionController)
2137 return;
edchinbb8ba892017-09-12 15:44:032138 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2139 initWithBrowserState:_browserState
2140 loader:self
2141 parentController:self
2142 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132143}
2144
2145// Update the state of back and forward buttons, hiding the forward button if
2146// there is nowhere to go. Assumes the model's current tab is up to date.
2147- (void)updateToolbar {
2148 // If the BVC has been partially torn down for low memory, wait for the
2149 // view rebuild to handle toolbar updates.
2150 if (!(_toolbarModelIOS && _browserState))
2151 return;
2152
2153 Tab* tab = [_model currentTab];
2154 if (![tab navigationManager])
2155 return;
sczsf1620e52017-10-02 22:54:462156 [_toolbarCoordinator updateToolbarState];
2157 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132158
Rohit Rao44f204302017-08-10 14:49:542159 PrerenderService* prerenderService =
2160 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2161 BOOL isPrerenderTab =
2162 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2163 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462164 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132165
2166 // Also update the loading state for the tools menu (that is really an
2167 // extension of the toolbar on the iPhone).
2168 if (!IsIPadIdiom())
sczsf1620e52017-10-02 22:54:462169 [[_toolbarCoordinator toolsPopupController]
sdefresnee65fd872016-12-19 13:38:132170 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2171
rohitrao005a6432017-03-16 20:52:422172 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2173 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132174 [self showFindBarWithAnimation:NO
2175 selectText:YES
2176 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362177 }
sdefresnee65fd872016-12-19 13:38:132178
2179 // Hide the toolbar if displaying phone NTP.
2180 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552181 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132182 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552183 if (item) {
2184 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132185 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2186 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462187 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2188 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132189 }
sczs42f7f7482017-11-08 01:13:272190 [_toolbarCoordinator.toolbarViewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132191 }
2192}
2193
2194- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122195 self.dialogPresenter.active =
2196 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132197}
2198
2199- (void)dismissPopups {
sczsf1620e52017-10-02 22:54:462200 [_toolbarCoordinator dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272201 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282202 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472203 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382204}
2205
Cooper Knaakd0a974cd2017-08-10 18:05:472206- (BubbleViewControllerPresenter*)
2207bubblePresenterForFeature:(const base::Feature&)feature
2208 direction:(BubbleArrowDirection)direction
2209 alignment:(BubbleAlignment)alignment
2210 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212211 DCHECK(self.browserState);
2212 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382213 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472214 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382215 }
2216 // Capture |weakSelf| instead of the feature engagement tracker object
2217 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2218 // the feature engagement tracker will remain pointing to invalid memory if
2219 // its owner (the ChromeBrowserState) is deallocated.
2220 __weak BrowserViewController* weakSelf = self;
2221 void (^dismissalCallback)(void) = ^() {
2222 BrowserViewController* strongSelf = weakSelf;
2223 if (strongSelf) {
2224 feature_engagement::TrackerFactory::GetForBrowserState(
2225 strongSelf.browserState)
2226 ->Dismissed(feature);
2227 }
2228 };
2229
Cooper Knaakd0a974cd2017-08-10 18:05:472230 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382231 [[BubbleViewControllerPresenter alloc] initWithText:text
2232 arrowDirection:direction
2233 alignment:alignment
2234 dismissalCallback:dismissalCallback];
2235
Cooper Knaakd0a974cd2017-08-10 18:05:472236 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132237}
2238
Cooper Knaak120cee5e2017-08-10 20:57:002239- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212240 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002241 // If the tab tip bubble has already been presented and the user is still
2242 // considered engaged, it can't be overwritten or set to |nil| or else it will
2243 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2244 // can be safely overwritten or set to |nil|.
2245 if (!self.tabTipBubblePresenter.isUserEngaged) {
2246 __weak BrowserViewController* weakSelf = self;
2247 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2248 [weakSelf presentNewTabTipBubble];
2249 };
2250
2251 // Because the new tab tip occurs on startup, the feature engagement
2252 // tracker's database is not guaranteed to be loaded by this time. For the
2253 // bubble to appear properly, a callback is used to guarantee the event data
2254 // is loaded before the check to see if the promotion should be displayed.
2255 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2256 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2257 }
2258}
2259
2260- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212261 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002262 NSString* text =
2263 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2264 CGPoint tabSwitcherAnchor;
2265 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032266 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002267 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032268 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002269 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2270 } else {
sczsf1620e52017-10-02 22:54:462271 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002272 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462273 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002274 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2275 }
Cooper Knaake963d6702017-08-11 21:03:112276 // If the feature engagement tracker does not consider it valid to display
2277 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2278 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002279 self.tabTipBubblePresenter =
2280 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2281 direction:BubbleArrowDirectionUp
2282 alignment:BubbleAlignmentTrailing
2283 text:text];
2284 [self.tabTipBubblePresenter presentInViewController:self
2285 view:self.view
2286 anchorPoint:tabSwitcherAnchor];
2287}
2288
Helen Yang9175bd52017-08-12 00:28:402289- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212290 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402291 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2292 // user is still considered engaged.
2293 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2294 __weak BrowserViewController* weakSelf = self;
2295 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2296 [weakSelf presentNewIncognitoTabTipBubble];
2297 };
2298
2299 // Use a callback in case the new incognito tab tip should be shown on
2300 // startup. This ensures that the tracker's database will be fully loaded
2301 // before checking if the promotion should be displayed.
2302 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2303 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2304 }
2305}
2306
2307- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212308 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462309 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402310 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2311 NSString* text = l10n_util::GetNSStringWithFixup(
2312 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462313 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402314 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2315 self.incognitoTabTipBubblePresenter =
2316 [self bubblePresenterForFeature:feature_engagement::
2317 kIPHNewIncognitoTabTipFeature
2318 direction:BubbleArrowDirectionUp
2319 alignment:BubbleAlignmentTrailing
2320 text:text];
2321 [self.incognitoTabTipBubblePresenter
2322 presentInViewController:self
2323 view:self.view
2324 anchorPoint:toolsButtonAnchor];
2325 // Only trigger the tools menu button animation if the bubble is shown.
2326 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462327 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402328 }
2329}
2330
sdefresnee65fd872016-12-19 13:38:132331#pragma mark - Tap handling
2332
Mark Cogandfcdea72017-07-18 13:47:382333- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032334 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2335 _lastTapPoint = CGPointZero;
2336 } else {
2337 _lastTapPoint =
2338 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132339 }
Mark Cogane01ebce2017-07-12 19:31:032340 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132341}
2342
2343- (CGPoint)lastTapPoint {
2344 if (CACurrentMediaTime() - _lastTapTime < 1) {
2345 return _lastTapPoint;
2346 }
2347 return CGPointZero;
2348}
2349
2350- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2351 UIView* view = gestureRecognizer.view;
2352 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2353 _lastTapPoint =
2354 [[view superview] convertPoint:viewCoordinate toView:self.view];
2355 _lastTapTime = CACurrentMediaTime();
2356}
2357
2358- (BOOL)addTabIfNoTabWithNormalBrowserState {
2359 if (![_model count]) {
2360 if (!_isOffTheRecord) {
2361 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2362 transition:ui::PAGE_TRANSITION_TYPED];
2363 return YES;
2364 }
2365 }
2366 return NO;
2367}
2368
2369#pragma mark - Tab creation and selection
2370
2371// Called when either a tab finishes loading or when a tab with finished content
2372// is added directly to the model via pre-rendering.
2373- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2374 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2375
2376 // Persist the session on a delay.
2377 [_model saveSessionImmediately:NO];
2378}
2379
2380- (Tab*)addSelectedTabWithURL:(const GURL&)url
2381 postData:(TemplateURLRef::PostContent*)postData
2382 transition:(ui::PageTransition)transition {
2383 return [self addSelectedTabWithURL:url
2384 postData:postData
2385 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492386 transition:transition
2387 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132388}
2389
2390- (Tab*)addSelectedTabWithURL:(const GURL&)url
2391 transition:(ui::PageTransition)transition {
2392 return [self addSelectedTabWithURL:url
2393 atIndex:[_model count]
2394 transition:transition];
2395}
2396
2397- (Tab*)addSelectedTabWithURL:(const GURL&)url
2398 atIndex:(NSUInteger)position
2399 transition:(ui::PageTransition)transition {
2400 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492401 atIndex:position
2402 transition:transition
2403 tabAddedCompletion:nil];
2404}
2405
2406- (Tab*)addSelectedTabWithURL:(const GURL&)url
2407 atIndex:(NSUInteger)position
2408 transition:(ui::PageTransition)transition
2409 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2410 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132411 postData:NULL
2412 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492413 transition:transition
2414 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132415}
2416
2417- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2418 postData:(TemplateURLRef::PostContent*)postData
2419 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492420 transition:(ui::PageTransition)transition
2421 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132422 if (position == NSNotFound)
2423 position = [_model count];
2424 DCHECK(position <= [_model count]);
2425
2426 web::NavigationManager::WebLoadParams params(URL);
2427 params.transition_type = transition;
2428 if (postData) {
2429 // Extract the content type and post params from |postData| and add them
2430 // to the load params.
2431 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2432 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2433 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172434 params.post_data.reset(data);
2435 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132436 }
Olivier Robind508a5632017-07-19 16:29:492437
2438 if (tabAddedCompletion) {
2439 if (self.foregroundTabWasAddedCompletionBlock) {
2440 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2441 self.foregroundTabWasAddedCompletionBlock;
2442 self.foregroundTabWasAddedCompletionBlock = ^{
2443 oldForegroundTabWasAddedCompletionBlock();
2444 tabAddedCompletion();
2445 };
2446 } else {
2447 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2448 }
2449 }
2450
sdefresnea6395912017-03-01 01:14:352451 Tab* tab = [_model insertTabWithLoadParams:params
2452 opener:nil
2453 openedByDOM:NO
2454 atIndex:position
2455 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132456 return tab;
2457}
2458
olivierrobin889af53f2017-03-01 14:56:322459// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132460- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322461 web::WebState* webState = tab.webState;
2462 if (!webState)
2463 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392464 web::NavigationItem* visibleItem =
2465 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322466 if (!visibleItem)
2467 return NO;
2468 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132469}
2470
2471- (void)expectNewForegroundTab {
2472 _expectingForegroundTab = YES;
2473}
2474
2475- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2476 CGRect viewBounds, remainder;
2477 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2478 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172479 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132480}
2481
2482- (UIImageView*)pageOpenCloseAnimationView {
2483 CGRect frame = [_contentArea bounds];
2484
2485 frame.size.height = frame.size.height - [self headerHeight];
2486 frame.origin.y = [self headerHeight];
2487
stkhapuginf58b10d02017-04-10 13:36:172488 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132489 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2490 pageView.center = center;
2491
2492 pageView.backgroundColor = [UIColor whiteColor];
2493 return pageView;
2494}
2495
2496- (void)installDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112497 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
sdefresne49cf2862017-03-15 13:46:142498 // Unregistration happens when the Tab is removed from the TabModel.
edchincd32fdf2017-10-25 12:45:452499
2500 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2501 if (PasswordTabHelper* passwordTabHelper =
2502 PasswordTabHelper::FromWebState(tab.webState)) {
2503 passwordTabHelper->SetDispatcher(self.dispatcher);
2504 passwordTabHelper->SetPasswordControllerDelegate(self);
2505 }
2506
sdefresnee65fd872016-12-19 13:38:132507 tab.dialogDelegate = self;
2508 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132509 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092510 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472511 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372512 }
sdefresnee65fd872016-12-19 13:38:132513 if (!IsIPadIdiom()) {
2514 tab.overscrollActionsControllerDelegate = self;
2515 }
olivierrobin9ce77b82017-01-12 17:29:192516 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132517 tab.tabSnapshottingDelegate = self;
2518 // Install the proper CRWWebController delegates.
2519 tab.webController.nativeProvider = self;
2520 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512521 // BrowserViewController presents SKStoreKitViewController on behalf of a
2522 // tab.
2523 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2524 if (tabHelper)
2525 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132526 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532527 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292528 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532529 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292530 }
Sylvain Defresnecacc3a52017-09-12 13:51:042531 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592532 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572533 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092534 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
edchincd32fdf2017-10-25 12:45:452535
2536 if (AccountConsistencyService* accountConsistencyService =
2537 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2538 self.browserState)) {
2539 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552540 }
sdefresnee65fd872016-12-19 13:38:132541}
2542
sdefresne49cf2862017-03-15 13:46:142543- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112544 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452545
2546 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2547 if (PasswordTabHelper* passwordTabHelper =
2548 PasswordTabHelper::FromWebState(tab.webState))
2549 passwordTabHelper->SetDispatcher(nil);
2550
sdefresne49cf2862017-03-15 13:46:142551 tab.dialogDelegate = nil;
2552 tab.snapshotOverlayProvider = nil;
2553 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092554 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472555 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372556 }
sdefresne49cf2862017-03-15 13:46:142557 if (!IsIPadIdiom()) {
2558 tab.overscrollActionsControllerDelegate = nil;
2559 }
2560 tab.tabHeadersDelegate = nil;
2561 tab.tabSnapshottingDelegate = nil;
2562 tab.webController.nativeProvider = nil;
2563 tab.webController.swipeRecognizerProvider = nil;
2564 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2565 if (tabHelper)
2566 tabHelper->SetLauncher(nil);
2567 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452568 if (AccountConsistencyService* accountConsistencyService =
2569 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2570 self.browserState)) {
2571 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2572 }
sdefresne49cf2862017-03-15 13:46:142573}
2574
sdefresnee65fd872016-12-19 13:38:132575// Called when a tab is selected in the model. Make any required view changes.
2576// The notification will not be sent when the tab is already the selected tab.
2577- (void)tabSelected:(Tab*)tab {
2578 DCHECK(tab);
2579
2580 // Ignore changes while the tab stack view is visible (or while suspended).
2581 // The display will be refreshed when this view becomes active again.
2582 if (!self.visible || ![_model webUsageEnabled])
2583 return;
2584
2585 [self displayTab:tab isNewSelection:YES];
2586
kkhorimotoa44349c12017-04-12 23:02:122587 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132588 // Now that the new tab has been displayed, return to normal. Rather than
2589 // keep a reference to the previous tab, just turn off preview mode for all
2590 // tabs (since doing so is a no-op for the tabs that don't have it set).
2591 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272592 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132593 [tab.webController setOverlayPreviewMode:NO];
2594 }
2595 }
2596}
2597
edchinf5150c682017-09-18 02:50:032598- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2599 return [self.tabStripCoordinator placeholderView];
2600}
2601
Sylvain Defresne41170aa2017-06-15 10:25:202602- (void)shutdown {
2603 DCHECK(!_isShutdown);
2604 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032605 [self.tabStripCoordinator stop];
2606 self.tabStripCoordinator = nil;
sczs42f7f7482017-11-08 01:13:272607 [_toolbarCoordinator stop];
2608 _toolbarCoordinator = nil;
edchinf5150c682017-09-18 02:50:032609 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202610 _infoBarContainer = nil;
2611 _readingListMenuNotifier = nil;
2612 if (_bookmarkModel)
2613 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2614 [_model removeObserver:self];
2615 [[UpgradeCenter sharedInstance] unregisterClient:self];
2616 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052617 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202618 if (_voiceSearchController)
2619 _voiceSearchController->SetDelegate(nil);
2620 [_rateThisAppDialog setDelegate:nil];
2621 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462622 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202623}
2624
sdefresnee65fd872016-12-19 13:38:132625#pragma mark - SnapshotOverlayProvider methods
2626
2627- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2628 NSMutableArray* overlays = [NSMutableArray array];
2629 if (![_model webUsageEnabled]) {
2630 return overlays;
2631 }
2632 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2633 if (voiceSearchView) {
2634 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272635 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132636 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272637 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132638 [overlays addObject:voiceSearchOverlay];
2639 }
2640 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2641 if (infoBarView) {
2642 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272643 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132644 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272645 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132646 [overlays addObject:infoBarOverlay];
2647 }
2648 return overlays;
2649}
2650
2651#pragma mark -
2652
2653- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2654 if (IsIPadIdiom()) {
2655 // Not using overlays on iPad because the content is pushed down by
2656 // infobar and the transition between snapshot and fresh page can
2657 // cause both snapshot and real infobars to appear at the same time.
2658 return nil;
2659 }
2660 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302661 if (currentTab && tab == currentTab) {
2662 DCHECK(currentTab.webState);
2663 infobars::InfoBarManager* infoBarManager =
2664 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132665 if (infoBarManager->infobar_count() > 0) {
2666 DCHECK(_infoBarContainer);
2667 return _infoBarContainer->view();
2668 }
2669 }
2670 return nil;
2671}
2672
2673- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272674 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132675 // There is no UI representation for non-current tabs or there is
2676 // no _infoBarContainer instantiated yet.
2677 // Return offset outside of tab.
2678 return CGRectGetMaxY(self.view.frame);
2679 } else if (IsIPadIdiom()) {
2680 // The infobars on iPad are display at the top of a tab.
2681 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2682 } else {
2683 // The infobars on iPhone are displayed at the bottom of a tab.
2684 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2685 return CGRectGetMaxY(visibleFrame) -
2686 CGRectGetHeight(_infoBarContainer->view().frame);
2687 }
2688}
2689
2690- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2691 Tab* currentTab = [_model currentTab];
2692 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2693 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2694 return _voiceSearchBar;
2695 }
2696 return nil;
2697}
2698
2699- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2700 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2701 // There is no UI representation for non-current tabs or there is
2702 // no visible voice search. Return offset outside of tab.
2703 return CGRectGetMaxY(self.view.frame);
2704 } else {
2705 // The voice search bar on iPhone is displayed at the bottom of a tab.
2706 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2707 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2708 }
2709}
2710
2711- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272712 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132713 VoiceSearchProvider* provider =
2714 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2715 if (provider) {
2716 _voiceSearchController =
2717 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462718 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052719 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132720 }
2721 }
2722}
2723
2724- (void)ensureVoiceSearchBarCreated {
2725 if (_voiceSearchBar)
2726 return;
2727
2728 CGFloat width = CGRectGetWidth([[self view] bounds]);
2729 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2730 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272731 _voiceSearchBar = ios::GetChromeBrowserProvider()
2732 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002733 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132734 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2735 [_voiceSearchBar setHidden:YES];
2736 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2737 UIViewAutoresizingFlexibleWidth];
2738 [self.view insertSubview:_voiceSearchBar
2739 belowSubview:_infoBarContainer->view()];
2740}
2741
2742- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2743 // Voice search bar exists and is shown/hidden.
2744 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272745 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132746 return;
2747
2748 // Voice search bar doesn't exist and thus is not visible.
2749 if (!_voiceSearchBar && !show)
2750 return;
2751
2752 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272753 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132754 else
stkhapuginc9eee7b2017-04-10 15:49:272755 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132756}
2757
2758- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2759 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2760 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2761 self.shouldShowVoiceSearchBar) {
2762 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2763 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272764 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132765 }
2766 id currentNativeController =
2767 [self nativeControllerForTab:self.tabModel.currentTab];
2768 Protocol* possibleOwnerProtocol =
2769 @protocol(LogoAnimationControllerOwnerOwner);
2770 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2771 [currentNativeController logoAnimationControllerOwner]) {
2772 // If the current native controller is showing a GLIF view (e.g. the NTP
2773 // when there is no doodle), use that GLIFControllerOwner.
2774 return [currentNativeController logoAnimationControllerOwner];
2775 }
2776 return nil;
2777}
2778
2779#pragma mark - PassKitDialogProvider methods
2780
2781- (void)presentPassKitDialog:(NSData*)data {
2782 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272783 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132784 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272785 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132786 if (error || !data) {
2787 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302788 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132789 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302790 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132791 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2792 // NULL, replace if with DCHECK).
2793 if (infoBarManager)
2794 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2795 }
2796 } else {
2797 PKAddPassesViewController* passKitViewController =
2798 [_dependencyFactory newPassKitViewControllerForPass:pass];
2799 if (passKitViewController) {
2800 [self presentViewController:passKitViewController
2801 animated:YES
2802 completion:^{
2803 }];
2804 }
2805 }
2806}
2807
2808- (UIStatusBarStyle)preferredStatusBarStyle {
2809 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2810 : UIStatusBarStyleDefault;
2811}
2812
Tomasz Garbusb844e992017-09-29 12:44:552813#pragma mark - PasswordControllerDelegate methods
2814
2815- (BOOL)displaySignInNotification:(UIViewController*)viewController
2816 fromTabId:(NSString*)tabId {
2817 // Check if the call comes from currently visible tab.
2818 if ([tabId isEqual:[_model currentTab].tabId]) {
2819 [self addChildViewController:viewController];
2820 [self.view addSubview:viewController.view];
2821 [viewController didMoveToParentViewController:self];
2822 return YES;
2823 } else {
2824 return NO;
2825 }
2826}
2827
sdefresnee65fd872016-12-19 13:38:132828#pragma mark - CRWWebStateDelegate methods.
2829
eugenebut75a06fa72017-01-09 17:09:552830- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512831 createNewWebStateForURL:(const GURL&)URL
2832 openerURL:(const GURL&)openerURL
2833 initiatedByUser:(BOOL)initiatedByUser {
2834 // Check if requested web state is a popup and block it if necessary.
2835 if (!initiatedByUser) {
2836 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2837 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102838 // It's possible for a page to inject a popup into a window created via
2839 // window.open before its initial load is committed. Rather than relying
2840 // on the last committed or pending NavigationItem's referrer policy, just
2841 // use ReferrerPolicyDefault.
2842 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2843 // policy once referrer policies are correctly recorded in
2844 // NavigationItems.
2845 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512846 helper->HandlePopup(URL, referrer);
2847 return nil;
2848 }
2849 }
2850
2851 // Requested web state should not be blocked from opening.
2852 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2853 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2854
2855 // Tabs open by DOM are always renderer initiated.
2856 web::NavigationManager::WebLoadParams params(GURL{});
2857 params.transition_type = ui::PAGE_TRANSITION_LINK;
2858 params.is_renderer_initiated = true;
2859 Tab* childTab = [[self tabModel]
2860 insertTabWithLoadParams:params
2861 opener:currentTab
2862 openedByDOM:YES
2863 atIndex:TabModelConstants::kTabPositionAutomatically
2864 inBackground:NO];
2865 return childTab.webState;
2866}
2867
eugenebutb46b2122017-03-14 02:43:262868- (void)closeWebState:(web::WebState*)webState {
2869 // Only allow a web page to close itself if it was opened by DOM, or if there
2870 // are no navigation items.
2871 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582872 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262873
2874 if (![self tabModel])
2875 return;
2876
2877 NSUInteger index = [[self tabModel] indexOfTab:tab];
2878 if (index != NSNotFound)
2879 [[self tabModel] closeTabAtIndex:index];
2880}
2881
eugenebut275f5892017-03-09 22:20:512882- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552883 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2884 switch (params.disposition) {
2885 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2886 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2887 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352888 insertTabWithURL:params.url
2889 referrer:params.referrer
2890 transition:params.transition
2891 opener:LegacyTabHelper::GetTabForWebState(webState)
2892 openedByDOM:NO
2893 atIndex:TabModelConstants::kTabPositionAutomatically
2894 inBackground:(params.disposition ==
2895 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552896 return tab.webState;
2897 }
2898 case WindowOpenDisposition::CURRENT_TAB: {
2899 web::NavigationManager::WebLoadParams loadParams(params.url);
2900 loadParams.referrer = params.referrer;
2901 loadParams.transition_type = params.transition;
2902 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2903 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2904 return webState;
2905 }
eugenebutd0984e82017-02-22 23:47:512906 case WindowOpenDisposition::NEW_POPUP: {
2907 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352908 insertTabWithURL:params.url
2909 referrer:params.referrer
2910 transition:params.transition
2911 opener:LegacyTabHelper::GetTabForWebState(webState)
2912 openedByDOM:YES
2913 atIndex:TabModelConstants::kTabPositionAutomatically
2914 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512915 return tab.webState;
2916 }
eugenebut75a06fa72017-01-09 17:09:552917 default:
2918 NOTIMPLEMENTED();
2919 return nullptr;
2920 };
2921}
2922
Mike Dougherty4e6b3a32017-08-23 18:49:212923- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132924 handleContextMenu:(const web::ContextMenuParams&)params {
2925 // Prevent context menu from displaying for a tab which is no longer the
2926 // current one.
2927 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212928 return;
sdefresnee65fd872016-12-19 13:38:132929 }
2930
2931 // No custom context menu if no valid url is available in |params|.
2932 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212933 return;
sdefresnee65fd872016-12-19 13:38:132934 }
2935
2936 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132937
stkhapuginc9eee7b2017-04-10 15:49:272938 _contextMenuCoordinator =
2939 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2940 params:params];
sdefresnee65fd872016-12-19 13:38:132941
2942 NSString* title = nil;
2943 ProceduralBlock action = nil;
2944
stkhapuginc9eee7b2017-04-10 15:49:272945 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132946 GURL link = params.link_url;
2947 bool isLink = link.is_valid();
2948 GURL imageUrl = params.src_url;
2949 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192950 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132951
2952 if (isLink) {
2953 if (link.SchemeIs(url::kJavaScriptScheme)) {
2954 // Open
2955 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2956 action = ^{
2957 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2958 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2959 };
2960 [_contextMenuCoordinator addItemWithTitle:title action:action];
2961 }
2962
2963 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192964 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132965
sdefresnee65fd872016-12-19 13:38:132966 // Open in New Tab.
2967 title = l10n_util::GetNSStringWithFixup(
2968 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2969 action = ^{
2970 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002971 // The "New Tab" item in the context menu opens a new tab in the current
2972 // browser state. |isOffTheRecord| indicates whether or not the current
2973 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132974 [weakSelf webPageOrderedOpen:link
2975 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002976 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132977 inBackground:YES
2978 appendTo:kCurrentTab];
2979 };
2980 [_contextMenuCoordinator addItemWithTitle:title action:action];
2981 if (!_isOffTheRecord) {
2982 // Open in Incognito Tab.
2983 title = l10n_util::GetNSStringWithFixup(
2984 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2985 action = ^{
2986 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2987 [weakSelf webPageOrderedOpen:link
2988 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132989 inIncognito:YES
2990 inBackground:NO
2991 appendTo:kCurrentTab];
2992 };
2993 [_contextMenuCoordinator addItemWithTitle:title action:action];
2994 }
olivierrobin51d4cf42017-01-17 13:32:352995 }
gambard65d69152017-03-23 17:44:222996 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352997 NSString* innerText = params.link_text;
2998 if ([innerText length] > 0) {
2999 // Add to reading list.
3000 title = l10n_util::GetNSStringWithFixup(
3001 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
3002 action = ^{
3003 Record(ACTION_READ_LATER, isImage, isLink);
3004 [weakSelf addToReadingListURL:link title:innerText];
3005 };
3006 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:533007 }
sdefresnee65fd872016-12-19 13:38:133008 }
3009 // Copy Link.
3010 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
3011 action = ^{
3012 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:283013 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133014 };
3015 [_contextMenuCoordinator addItemWithTitle:title action:action];
3016 }
3017 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193018 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133019 // Save Image.
gambard98b4ddf2017-04-18 07:14:053020 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133021 action = ^{
3022 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3023 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3024 };
3025 [_contextMenuCoordinator addItemWithTitle:title action:action];
3026 // Open Image.
3027 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3028 action = ^{
3029 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3030 [weakSelf loadURL:imageUrl
3031 referrer:referrer
3032 transition:ui::PAGE_TRANSITION_LINK
3033 rendererInitiated:YES];
3034 };
3035 [_contextMenuCoordinator addItemWithTitle:title action:action];
3036 // Open Image In New Tab.
3037 title = l10n_util::GetNSStringWithFixup(
3038 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3039 action = ^{
3040 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3041 [weakSelf webPageOrderedOpen:imageUrl
3042 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133043 inBackground:true
3044 appendTo:kCurrentTab];
3045 };
3046 [_contextMenuCoordinator addItemWithTitle:title action:action];
3047
3048 TemplateURLService* service =
3049 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103050 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133051 if (defaultURL && !defaultURL->image_url().empty() &&
3052 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3053 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3054 defaultURL->short_name());
3055 action = ^{
3056 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3057 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3058 };
3059 [_contextMenuCoordinator addItemWithTitle:title action:action];
3060 }
3061 }
3062
3063 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133064}
3065
eugenebutb739bdc2017-01-25 06:32:483066- (void)webState:(web::WebState*)webState
3067 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3068 // Display the action sheet with the arrow pointing at the top center of the
3069 // web contents.
sdefresne0452a9d2017-02-09 15:33:283070 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483071 UIView* view = webState->GetView();
3072 CGPoint dialogLocation =
3073 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283074 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033075 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173076 helper->PresentDialog(dialogLocation,
3077 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053078 handler(shouldContinue);
3079 }));
eugenebutb739bdc2017-01-25 06:32:483080}
3081
sdefresnee65fd872016-12-19 13:38:133082- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3083 (web::WebState*)webState {
3084 return _javaScriptDialogPresenter.get();
3085}
3086
eugenebut63232102017-01-19 16:19:403087- (void)webState:(web::WebState*)webState
3088 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3089 proposedCredential:(NSURLCredential*)proposedCredential
3090 completionHandler:(void (^)(NSString* username,
3091 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:423092 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3093 if ([tab isPrerenderTab]) {
3094 [tab discardPrerender];
3095 if (handler) {
3096 handler(nil, nil);
3097 }
3098 return;
3099 }
3100
eugenebut63232102017-01-19 16:19:403101 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3102 proposedCredential:proposedCredential
3103 webState:webState
3104 completionHandler:handler];
3105}
3106
Kurt Horimoto62e97c72017-11-03 19:51:473107#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133108
3109- (CGFloat)headerOffset {
3110 if (IsIPadIdiom())
3111 return StatusBarHeight();
3112 return 0.0;
3113}
3114
stkhapugin952ecef2017-04-11 12:11:453115- (NSArray<HeaderDefinition*>*)headerViews {
3116 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133117 if (![self isViewLoaded])
3118 return results;
3119
3120 if (!IsIPadIdiom()) {
sczs42f7f7482017-11-08 01:13:273121 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453122 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273123 definitionWithView:_toolbarCoordinator
3124 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453125 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243126 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453127 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133128 }
3129 } else {
edchinf5150c682017-09-18 02:50:033130 if (self.tabStripView) {
3131 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3132 headerBehaviour:Hideable
3133 heightAdjustment:0.0
3134 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133135 }
sczs42f7f7482017-11-08 01:13:273136 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453137 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273138 definitionWithView:_toolbarCoordinator
3139 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453140 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243141 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453142 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133143 }
3144 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453145 [results addObject:[HeaderDefinition
3146 definitionWithView:[_findBarController view]
3147 headerBehaviour:Overlap
3148 heightAdjustment:0.0
3149 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133150 }
3151 }
stkhapugin952ecef2017-04-11 12:11:453152 return [results copy];
sdefresnee65fd872016-12-19 13:38:133153}
3154
3155- (UIView*)footerView {
3156 return _voiceSearchBar;
3157}
3158
3159- (CGFloat)headerHeight {
3160 return [self headerHeightForTab:[_model currentTab]];
3161}
3162
3163- (CGFloat)headerHeightForTab:(Tab*)tab {
3164 id nativeController = [self nativeControllerForTab:tab];
3165 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3166 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3167 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3168 // On iPhone, don't add any header height for ToolbarOwner native
3169 // controllers when they're displaying their own toolbar.
3170 return 0;
3171 }
3172
stkhapugin952ecef2017-04-11 12:11:453173 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133174
3175 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453176 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133177 if (header.view && header.behaviour == Hideable) {
3178 height += CGRectGetHeight([header.view frame]) -
3179 header.heightAdjustement - header.inset;
3180 }
3181 }
3182
3183 return height - StatusBarHeight();
3184}
3185
3186- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193187 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133188}
3189
3190- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453191 NSArray<HeaderDefinition*>* headers = [self headerViews];
3192 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133193 return 0.0;
3194
3195 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3196 // API documentation.
3197 if ([[[self tabModel] currentTab] isPrerenderTab])
3198 return [self headerHeight];
3199
3200 UIView* topHeader = headers[0].view;
3201 return -(topHeader.frame.origin.y - [self headerOffset]);
3202}
3203
3204- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3205 UIView* footer = [self footerView];
3206 CGFloat headerHeight = [self headerHeight];
3207 if (!footer || headerHeight == 0)
3208 return 0.0;
3209
3210 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3211 CGFloat offset = headerOffset * footerHeight / headerHeight;
3212 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3213}
3214
Kurt Horimoto62e97c72017-11-03 19:51:473215- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:133216 headerAnimationCompleted:(BOOL)completed
3217 offset:(CGFloat)offset {
3218 if (completed)
justincohen04c27772016-12-21 20:16:593219 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133220}
3221
stkhapugin952ecef2017-04-11 12:11:453222- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133223 atOffset:(CGFloat)headerOffset {
3224 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453225 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133226 CGRect frame = [header.view frame];
3227 frame.origin.y = height - headerOffset - header.inset;
3228 [header.view setFrame:frame];
3229 if (header.behaviour != Overlap)
3230 height += CGRectGetHeight(frame);
3231 }
3232}
3233
Kurt Horimoto62e97c72017-11-03 19:51:473234- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133235 drawHeaderViewFromOffset:(CGFloat)headerOffset
3236 animate:(BOOL)animate {
3237 if ([_sideSwipeController inSwipe])
3238 return;
3239
3240 CGRect footerFrame = CGRectZero;
3241 UIView* footer = nil;
3242 // Only animate the voice search bar if the tab is a voice search results tab.
3243 if ([_model currentTab].isVoiceSearchResultsTab) {
3244 footer = [self footerView];
3245 footerFrame = footer.frame;
3246 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3247 }
3248
stkhapugin952ecef2017-04-11 12:11:453249 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133250 void (^block)(void) = ^{
3251 [self setFramesForHeaders:headers atOffset:headerOffset];
3252 footer.frame = footerFrame;
3253 };
3254 void (^completion)(BOOL) = ^(BOOL finished) {
3255 [self fullScreenController:fullScreenController
3256 headerAnimationCompleted:finished
3257 offset:headerOffset];
3258 };
3259 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473260 [UIView
3261 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3262 delay:0.0
3263 options:UIViewAnimationOptionBeginFromCurrentState
3264 animations:block
3265 completion:completion];
sdefresnee65fd872016-12-19 13:38:133266 } else {
3267 block();
3268 completion(YES);
3269 }
3270}
3271
Kurt Horimoto62e97c72017-11-03 19:51:473272- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133273 drawHeaderViewFromOffset:(CGFloat)headerOffset
3274 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3275 changeTopContentPadding:(BOOL)changeTopContentPadding
3276 scrollingToOffset:(CGFloat)contentOffset {
3277 DCHECK(webViewProxy);
3278 if ([_sideSwipeController inSwipe])
3279 return;
3280
3281 CGRect footerFrame;
3282 UIView* footer = nil;
3283 // Only animate the voice search bar if the tab is a voice search results tab.
3284 if ([_model currentTab].isVoiceSearchResultsTab) {
3285 footer = [self footerView];
3286 footerFrame = footer.frame;
3287 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3288 }
3289
stkhapugin952ecef2017-04-11 12:11:453290 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133291 void (^block)(void) = ^{
3292 [self setFramesForHeaders:headers atOffset:headerOffset];
3293 footer.frame = footerFrame;
3294 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3295 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3296 if (changeTopContentPadding)
3297 webViewProxy.topContentPadding = contentOffset;
3298 };
3299 void (^completion)(BOOL) = ^(BOOL finished) {
3300 [self fullScreenController:fullScreenController
3301 headerAnimationCompleted:finished
3302 offset:headerOffset];
3303 };
3304
Kurt Horimoto62e97c72017-11-03 19:51:473305 [UIView
3306 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3307 delay:0.0
3308 options:UIViewAnimationOptionBeginFromCurrentState
3309 animations:block
3310 completion:completion];
sdefresnee65fd872016-12-19 13:38:133311}
3312
3313#pragma mark - VoiceSearchBarOwner
3314
3315- (id<VoiceSearchBar>)voiceSearchBar {
3316 return _voiceSearchBar;
3317}
3318
3319#pragma mark - Install OverScrollActionController method.
3320- (void)setOverScrollActionControllerToStaticNativeContent:
3321 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343322 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133323 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173324 [[OverscrollActionsController alloc]
3325 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133326 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053327 OverscrollStyle style = _isOffTheRecord
3328 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3329 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133330 controller.style = style;
3331 nativeContent.overscrollActionsController = controller;
3332 }
3333}
3334
3335#pragma mark - OverscrollActionsControllerDelegate methods.
3336
3337- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053338 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133339 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053340 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383341 [self.dispatcher
3342 openNewTab:[OpenNewTabCommand
3343 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133344 break;
rohitrao922b7111c2017-01-03 14:31:053345 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243346 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133347 break;
liaoyuke563dc4a2017-03-17 18:36:293348 case OverscrollAction::REFRESH: {
liaoyuke563dc4a2017-03-17 18:36:293349 web::WebState* webState = [_model currentTab].webState;
Eugene But083b6c7a2017-10-02 15:49:383350 if (webState) {
3351 if (webState->IsLoading()) {
3352 webState->Stop();
3353 }
liaoyuke563dc4a2017-03-17 18:36:293354 // |check_for_repost| is true because the reload is explicitly initiated
3355 // by the user.
3356 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3357 true /* check_for_repost */);
Eugene But083b6c7a2017-10-02 15:49:383358 }
sdefresnee65fd872016-12-19 13:38:133359 break;
liaoyuke563dc4a2017-03-17 18:36:293360 }
rohitrao922b7111c2017-01-03 14:31:053361 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133362 NOTREACHED();
3363 break;
3364 }
3365}
3366
3367- (BOOL)shouldAllowOverscrollActions {
3368 return YES;
3369}
3370
3371- (UIView*)headerView {
sczs42f7f7482017-11-08 01:13:273372 return _toolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133373}
3374
3375- (UIView*)toolbarSnapshotView {
sczs42f7f7482017-11-08 01:13:273376 return [_toolbarCoordinator.toolbarViewController.view
3377 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133378}
3379
3380- (CGFloat)overscrollActionsControllerHeaderInset:
3381 (OverscrollActionsController*)controller {
3382 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3383 return [self headerHeight];
3384 else
3385 return 0;
3386}
3387
3388- (CGFloat)overscrollHeaderHeight {
3389 return [self headerHeight] + StatusBarHeight();
3390}
3391
3392#pragma mark - TabSnapshottingDelegate methods.
3393
3394- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3395 CGRect pageContentArea = _contentArea.bounds;
3396 if ([_model webUsageEnabled])
3397 pageContentArea = tab.view.bounds;
3398 CGFloat headerHeight = [self headerHeightForTab:tab];
3399 id nativeController = [self nativeControllerForTab:tab];
3400 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3401 headerHeight += [nativeController toolbarHeight];
3402 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3403 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3404}
3405
3406#pragma mark - NewTabPageObserver methods.
3407
3408- (void)selectedPanelDidChange {
3409 [self updateToolbar];
3410}
3411
3412#pragma mark - CRWNativeContentProvider methods
3413
3414- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3415 withError:(NSError*)error
3416 isPost:(BOOL)isPost {
3417 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173418 [[ErrorPageContent alloc] initWithLoader:self
3419 browserState:self.browserState
3420 url:url
3421 error:error
3422 isPost:isPost
3423 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133424 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3425 return errorPageContent;
3426}
3427
3428- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003429 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453430 if (host == kChromeUIOfflineHost) {
3431 // Only allow offline URL that are fully specified.
3432 return reading_list::IsOfflineURLValid(
3433 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3434 }
sdefresnee65fd872016-12-19 13:38:133435
Justin Cohen8679e852017-08-14 16:35:253436 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003437 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253438 }
3439
3440 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133441}
3442
olivierrobind43eecb2017-01-27 20:35:263443- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3444 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133445 DCHECK(url.SchemeIs(kChromeUIScheme));
3446
3447 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003448 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193449 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003450 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463451 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3452 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3453 if (@available(iOS 11.0, *)) {
3454 safeAreaInset = self.view.safeAreaInsets;
3455 }
3456 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3457
sdefresnee65fd872016-12-19 13:38:133458 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173459 [[NewTabPageController alloc] initWithUrl:url
3460 loader:self
sczsf1620e52017-10-02 22:54:463461 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173462 ntpObserver:self
3463 browserState:_browserState
3464 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463465 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453466 tabModel:_model
justincohen75011c32017-04-28 16:31:393467 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463468 dispatcher:self.dispatcher
3469 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133470 pageController.swipeRecognizerProvider = self.sideSwipeController;
3471
3472 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233473 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133474
Marti Wong64481ec2017-10-31 03:38:003475 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133476 // New Tab Page can have multiple panels. Each panel is addressable
3477 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3478 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3479 // the Bookmark Manager, etc.
3480 // The utility functions NewTabPage::IdentifierFromFragment() and
3481 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3482 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3483 // without changing the URL since the URL may be chrome://bookmarks/#123.
3484 // If the URL is chrome://newtab/, pre-select the panel based on the
3485 // #fragment.
3486 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233487 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133488 : NewTabPage::IdentifierFromFragment(url.ref());
3489 }
3490 [pageController selectPanel:panelType];
3491 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453492 } else if (url_host == kChromeUIOfflineHost &&
3493 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133494 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173495 [[OfflinePageNativeContent alloc] initWithLoader:self
3496 browserState:_browserState
3497 webState:webState
3498 URL:url];
sdefresnee65fd872016-12-19 13:38:133499 [self setOverScrollActionControllerToStaticNativeContent:
3500 staticNativeController];
3501 nativeController = staticNativeController;
3502 } else if (url_host == kChromeUIExternalFileHost) {
3503 // Return an instance of the |ExternalFileController| only if the file is
3504 // still in the sandbox.
3505 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3506 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173507 nativeController =
3508 [[ExternalFileController alloc] initWithURL:url
3509 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133510 }
peterlaurens44615d02017-05-23 20:23:093511 } else if (url_host == kChromeUICrashHost) {
3512 // There is no native controller for kChromeUICrashHost, it is instead
3513 // handled as any other renderer crash by the SadTabTabHelper.
3514 // nativeController must be set to nil to prevent defaulting to a
3515 // PageNotAvailableController.
3516 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133517 } else {
3518 DCHECK(![self hasControllerForURL:url]);
3519 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173520 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133521 }
3522 // If a native controller is vended before its tab is added to the tab model,
3523 // use the temporary key and add it under the new tab's tabId in the
3524 // TabModelObserver callback. This happens:
3525 // - when there is no current tab (occurs when vending the NTP controller for
3526 // the first tab that is opened),
3527 // - when the current tab's url doesn't match |url| (occurs when a native
3528 // controller is opened in a new tab)
3529 // - when the current tab's url matches |url| and there is already a native
3530 // controller of the appropriate type vended to it (occurs when a native
3531 // controller is opened in a new tab from a tab with a matching URL, e.g.
3532 // opening an NTP when an NTP is already displayed in the current tab).
3533 // For normal page loads, history navigations, tab restorations, and crash
3534 // recoveries, the tab will already exist in the tab model and the tabId can
3535 // be used as the native controller key.
3536 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3537 // that native controllers vended here always correspond to the current tab.
3538 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193539 if (!currentTab.webState ||
3540 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443541 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133542 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443543 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133544 }
sdefresnee65fd872016-12-19 13:38:133545 return nativeController;
3546}
3547
3548- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443549 id nativeController = tab.webController.nativeController;
3550 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133551}
3552
3553#pragma mark - DialogPresenterDelegate methods
3554
3555- (void)dialogPresenter:(DialogPresenter*)presenter
3556 willShowDialogForWebState:(web::WebState*)webState {
3557 for (Tab* iteratedTab in self.tabModel) {
3558 if ([iteratedTab webState] == webState) {
3559 self.tabModel.currentTab = iteratedTab;
3560 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3561 break;
3562 }
3563 }
3564}
3565
3566#pragma mark - Context menu methods
3567
3568- (void)searchByImageAtURL:(const GURL&)url
3569 referrer:(const web::Referrer)referrer {
3570 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273571 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113572 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173573 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3574 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113575 DCHECK(data);
3576 dispatch_async(dispatch_get_main_queue(), ^{
3577 [weakSelf searchByImageData:data atURL:image_source_url];
3578 });
3579 };
3580 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133581 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3582 web::PolicyForNavigation(url, referrer));
3583}
3584
3585- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3586 NSData* imageData = data;
3587 UIImage* image = [UIImage imageWithData:imageData];
3588 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3589 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3590 // kSearchByImageMaxImageHeight).
3591 if (image &&
3592 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3593 (image.size.width > kSearchByImageMaxImageWidth ||
3594 image.size.height > kSearchByImageMaxImageHeight)) {
3595 CGSize newImageSize =
3596 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3597 image = [image gtm_imageByResizingToSize:newImageSize
3598 preserveAspectRatio:YES
3599 trimToFit:NO];
3600 imageData = UIImageJPEGRepresentation(image, 1.0);
3601 }
3602
3603 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3604 std::string byteString(bytes, [imageData length]);
3605
3606 TemplateURLService* templateUrlService =
3607 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103608 const TemplateURL* defaultURL =
3609 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133610 DCHECK(!defaultURL->image_url().empty());
3611 DCHECK(defaultURL->image_url_ref().IsValid(
3612 templateUrlService->search_terms_data()));
3613 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3614 search_args.image_url = imageURL;
3615 search_args.image_thumbnail_content = byteString;
3616
3617 // Generate the URL and populate |post_content| with the content type and
3618 // HTTP body for the request.
3619 TemplateURLRef::PostContent post_content;
3620 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3621 search_args, templateUrlService->search_terms_data(), &post_content));
3622 [self addSelectedTabWithURL:result
3623 postData:&post_content
3624 transition:ui::PAGE_TRANSITION_TYPED];
3625}
3626
3627- (void)saveImageAtURL:(const GURL&)url
3628 referrer:(const web::Referrer&)referrer {
3629 DCHECK(url.is_valid());
3630
gambard9efce7a2017-02-09 18:53:173631 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3632 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113633 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133634
gambardbbf85c42017-06-29 11:15:343635 if ([data length] == 0) {
3636 [self displayPrivacyErrorAlertOnMainQueue:
3637 l10n_util::GetNSString(
3638 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3639 return;
3640 }
3641
gambard9efce7a2017-02-09 18:53:173642 base::FilePath::StringType extension;
3643
3644 bool extensionSuccess =
3645 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3646 if (!extensionSuccess || extension.length() == 0) {
3647 extension = "png";
3648 }
3649
3650 NSString* fileExtension =
3651 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3652 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113653 };
3654 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133655 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3656 web::PolicyForNavigation(url, referrer));
3657}
3658
gambard9efce7a2017-02-09 18:53:173659- (void)managePermissionAndSaveImage:(NSData*)data
3660 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133661 switch ([PHPhotoLibrary authorizationStatus]) {
3662 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173663 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133664 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3665 // Call -saveImage again to check if chrome needs to display an error or
3666 // saves the image.
3667 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173668 [self managePermissionAndSaveImage:data
3669 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133670 }];
3671 break;
stkhapuginf58b10d02017-04-10 13:36:173672 }
sdefresnee65fd872016-12-19 13:38:133673
3674 // The application doesn't have permission to access photo and the user
3675 // cannot grant it.
3676 case PHAuthorizationStatusRestricted:
3677 [self displayPrivacyErrorAlertOnMainQueue:
3678 l10n_util::GetNSString(
3679 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3680 break;
3681
3682 // The application doesn't have permission to access photo and the user
3683 // can grant it.
3684 case PHAuthorizationStatusDenied:
3685 [self displayImageErrorAlertWithSettingsOnMainQueue];
3686 break;
3687
3688 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243689 default:
3690 __weak BrowserViewController* weakSelf = self;
3691 [self saveImage:data
3692 withFileExtension:fileExtension
3693 completion:^(BOOL success, NSError* error) {
3694 [weakSelf finishSavingImageWithError:error];
3695 }];
sdefresnee65fd872016-12-19 13:38:133696 break;
sdefresnee65fd872016-12-19 13:38:133697 }
3698}
3699
Sylvain Defresnefd3ecf22017-07-12 18:47:243700- (void)saveImage:(NSData*)data
3701 withFileExtension:(NSString*)fileExtension
3702 completion:(void (^)(BOOL, NSError*))completion {
3703 base::PostTaskWithTraits(
3704 FROM_HERE,
3705 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3706 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3707 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373708 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133709
Sylvain Defresnefd3ecf22017-07-12 18:47:243710 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3711 stringByAppendingString:fileExtension];
3712 NSURL* fileURL = [NSURL
3713 fileURLWithPath:[NSTemporaryDirectory()
3714 stringByAppendingPathComponent:fileName]];
3715 NSError* error = nil;
3716 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3717 if (error) {
3718 if (completion)
3719 completion(NO, error);
3720 return;
3721 }
sdefresnee65fd872016-12-19 13:38:133722
Sylvain Defresnefd3ecf22017-07-12 18:47:243723 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3724 [PHAssetChangeRequest
3725 creationRequestForAssetFromImageAtFileURL:fileURL];
3726 }
3727 completionHandler:^(BOOL success, NSError* error) {
3728 base::PostTaskWithTraits(
3729 FROM_HERE,
3730 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3731 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3732 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373733 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243734 if (completion)
3735 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133736
Sylvain Defresnefd3ecf22017-07-12 18:47:243737 // Cleanup the temporary file.
3738 NSError* deleteFileError = nil;
3739 [[NSFileManager defaultManager]
3740 removeItemAtURL:fileURL
3741 error:&deleteFileError];
3742 }));
3743 }];
3744 }));
sdefresnee65fd872016-12-19 13:38:133745}
3746
3747- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3748 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3749 BOOL canGoToSetting =
3750 [[UIApplication sharedApplication] canOpenURL:settingURL];
3751 if (canGoToSetting) {
3752 dispatch_async(dispatch_get_main_queue(), ^{
3753 [self displayImageErrorAlertWithSettings:settingURL];
3754 });
3755 } else {
3756 [self displayPrivacyErrorAlertOnMainQueue:
3757 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3758 }
3759}
3760
3761- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3762 // Dismiss current alert.
3763 [_alertCoordinator stop];
3764
3765 NSString* title =
3766 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3767 NSString* message = l10n_util::GetNSString(
3768 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3769
stkhapuginc9eee7b2017-04-10 15:49:273770 _alertCoordinator =
3771 [[AlertCoordinator alloc] initWithBaseViewController:self
3772 title:title
3773 message:message];
sdefresnee65fd872016-12-19 13:38:133774
3775 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3776 action:nil
3777 style:UIAlertActionStyleCancel];
3778
3779 [_alertCoordinator
3780 addItemWithTitle:l10n_util::GetNSString(
3781 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3782 action:^{
3783 OpenUrlWithCompletionHandler(settingURL, nil);
3784 }
3785 style:UIAlertActionStyleDefault];
3786
3787 [_alertCoordinator start];
3788}
3789
3790- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3791 dispatch_async(dispatch_get_main_queue(), ^{
3792 NSString* title =
3793 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3794 [self showErrorAlertWithStringTitle:title message:errorContent];
3795 });
3796}
3797
3798// This callback is triggered when the image is effectively saved onto the photo
3799// album, or if the save failed for some reason.
3800- (void)finishSavingImageWithError:(NSError*)error {
3801 // Was there an error?
3802 if (error) {
3803 // Saving photo failed even though user has granted access to Photos.
3804 // Display the error information from the NSError object for user.
3805 NSString* errorMessage = [NSString
3806 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3807 [error domain], [error code]];
3808 // This code may be execute outside of the main thread. Make sure to display
3809 // the error on the main thread.
3810 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3811 } else {
3812 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3813 // the photo application. The current behaviour is to create the photo there
3814 // but not providing any link to it is suboptimal. That's what Safari is
3815 // doing, and what the PM want, but it doesn't make it right.
3816 }
3817}
3818
sdefresnee65fd872016-12-19 13:38:133819#pragma mark - Showing popups
3820
sdefresnee65fd872016-12-19 13:38:133821- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133822 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3823
3824 ReadingListModel* readingModel =
3825 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013826 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3827 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133828
pinkerton07e27842017-03-02 15:29:023829 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363830 [self showSnackbar:l10n_util::GetNSString(
3831 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133832}
3833
3834#pragma mark - Keyboard commands management
3835
3836- (BOOL)shouldRegisterKeyboardCommands {
3837 if ([self presentedViewController])
3838 return NO;
3839
3840 if (_voiceSearchController && _voiceSearchController->IsVisible())
3841 return NO;
3842
3843 // If there is no first responder, try to make the webview the first
3844 // responder.
3845 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303846 web::WebState* webState = _model.currentTab.webState;
3847 if (webState)
3848 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133849 }
3850
3851 return YES;
3852}
3853
3854- (KeyCommandsProvider*)keyCommandsProvider {
3855 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273856 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133857 }
stkhapuginc9eee7b2017-04-10 15:49:273858 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133859}
3860
3861#pragma mark - KeyCommandsPlumbing
3862
3863- (BOOL)isOffTheRecord {
3864 return _isOffTheRecord;
3865}
3866
3867- (NSUInteger)tabsCount {
3868 return [_model count];
3869}
3870
lpromero47ea8862017-01-13 17:51:063871- (BOOL)canGoBack {
3872 return [_model currentTab].canGoBack;
3873}
3874
3875- (BOOL)canGoForward {
3876 return [_model currentTab].canGoForward;
3877}
3878
sdefresnee65fd872016-12-19 13:38:133879- (void)focusTabAtIndex:(NSUInteger)index {
3880 if ([_model count] > index) {
3881 [_model setCurrentTab:[_model tabAtIndex:index]];
3882 }
3883}
3884
3885- (void)focusNextTab {
3886 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3887 NSInteger modelCount = [_model count];
3888 if (currentTabIndex < modelCount - 1) {
3889 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3890 [_model setCurrentTab:nextTab];
3891 } else {
3892 [_model setCurrentTab:[_model tabAtIndex:0]];
3893 }
3894}
3895
3896- (void)focusPreviousTab {
3897 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3898 if (currentTabIndex > 0) {
3899 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3900 [_model setCurrentTab:previousTab];
3901 } else {
3902 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3903 [_model setCurrentTab:lastTab];
3904 }
3905}
3906
3907- (void)reopenClosedTab {
3908 sessions::TabRestoreService* const tabRestoreService =
3909 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3910 if (!tabRestoreService || tabRestoreService->entries().empty())
3911 return;
3912
3913 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3914 tabRestoreService->entries().front();
3915 // Only handle the TAB type.
3916 if (entry->type != sessions::TabRestoreService::TAB)
3917 return;
3918
Mark Cogandfcdea72017-07-18 13:47:383919 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133920 TabRestoreServiceDelegateImplIOS* const delegate =
3921 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3922 _browserState);
3923 tabRestoreService->RestoreEntryById(delegate, entry->id,
3924 WindowOpenDisposition::CURRENT_TAB);
3925}
3926
3927- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463928 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133929}
3930
3931#pragma mark - UIResponder
3932
3933- (NSArray*)keyCommands {
3934 if (![self shouldRegisterKeyboardCommands]) {
3935 return nil;
3936 }
3937 return [self.keyCommandsProvider
3938 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543939 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243940 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133941 editingText:![self isFirstResponder]];
3942}
3943
3944#pragma mark -
3945
3946// Induce an intentional crash in the browser process.
3947- (void)induceBrowserCrash {
3948 CHECK(false);
3949 // Call another function, so that the above CHECK can't be tail-call
3950 // optimized. This ensures that this method's name will show up in the stack
3951 // for easier identification.
3952 CHECK(true);
3953}
3954
3955- (void)loadURL:(const GURL&)url
3956 referrer:(const web::Referrer&)referrer
3957 transition:(ui::PageTransition)transition
3958 rendererInitiated:(BOOL)rendererInitiated {
3959 [[OmniboxGeolocationController sharedInstance]
3960 locationBarDidSubmitURL:url
3961 transition:transition
3962 browserState:_browserState];
3963
3964 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3965 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3966 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3967 }
3968
3969 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243970 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133971 // restart.
3972 if (url.host() == kChromeUIBrowserCrashHost) {
3973 [self induceBrowserCrash];
3974 // In debug the app can continue working even after the CHECK. Adding a
3975 // return avoids the crash url to be added to the history.
3976 return;
3977 }
3978
Danyao Wang85389a82017-10-25 18:56:273979 bool typed_or_generated_transition =
3980 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3981 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3982
Rohit Rao44f204302017-08-10 14:49:543983 PrerenderService* prerenderService =
3984 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3985 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593986 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543987 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593988 DCHECK(newWebState);
3989
sdefresnee65fd872016-12-19 13:38:133990 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593991 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133992 DCHECK(oldTab);
3993 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593994
kkhorimotod804c5732017-03-15 23:44:523995 bool canPruneItems =
3996 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593997
kkhorimotod804c5732017-03-15 23:44:523998 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523999 [newTab navigationManager]->CopyStateFromAndPrune(
4000 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:594001
4002 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
4003 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:134004
4005 // Set isPrerenderTab to NO after replacing the tab. This will allow the
4006 // BrowserViewController to detect that a pre-rendered tab is switched in,
4007 // and show the prerendering animation.
4008 newTab.isPrerenderTab = NO;
Danyao Wang85389a82017-10-25 18:56:274009 if (typed_or_generated_transition) {
4010 LoadTimingTabHelper::FromWebState(newTab.webState)
4011 ->DidPromotePrerenderTab();
4012 }
sdefresnee65fd872016-12-19 13:38:134013
sdefresne2f7781c2017-03-02 19:12:464014 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:134015 return;
4016 }
4017 }
4018
4019 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:544020 if (prerenderService) {
4021 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:134022 }
4023
sdefresnee65fd872016-12-19 13:38:134024 // Some URLs are not allowed while in incognito. If we are in incognito and
4025 // load a disallowed URL, instead create a new tab not in the incognito state.
4026 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4027 [self webPageOrderedOpen:url
4028 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134029 inIncognito:NO
4030 inBackground:NO
4031 appendTo:kCurrentTab];
4032 return;
4033 }
4034
Danyao Wang85389a82017-10-25 18:56:274035 if (typed_or_generated_transition) {
4036 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4037 ->DidInitiatePageLoad();
4038 }
4039
mrefaata84d5a02017-06-08 17:13:294040 // If this is a reload initiated from the omnibox.
4041 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4042 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4043 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4044 [[_model currentTab] navigationManager]->Reload(
4045 web::ReloadType::NORMAL, true /* check_for_repost */);
4046 return;
4047 }
4048
sdefresnee65fd872016-12-19 13:38:134049 web::NavigationManager::WebLoadParams params(urlToLoad);
4050 params.referrer = referrer;
4051 params.transition_type = transition;
4052 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104053 Tab* currentTab = [_model currentTab];
4054 DCHECK(currentTab);
4055 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4056 currentTab.navigationManager->LoadURLWithParams(params);
4057 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4058 // to be animated on screen because the transition animator will handle the
4059 // animations. When a Tab stops being a voice search Tab, the voice search
4060 // bar should be animated away.
4061 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4062 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134063}
4064
4065- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544066 PrerenderService* prerenderService =
4067 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4068 if (prerenderService) {
4069 prerenderService->CancelPrerender();
4070 }
sdefresnee65fd872016-12-19 13:38:134071 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184072 if ([self currentWebState])
4073 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134074}
4075
4076- (web::WebState*)currentWebState {
4077 return [[_model currentTab] webState];
4078}
4079
sdefresnee65fd872016-12-19 13:38:134080// Load a new URL on a new page/tab.
4081- (void)webPageOrderedOpen:(const GURL&)URL
4082 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134083 inBackground:(BOOL)inBackground
4084 appendTo:(OpenPosition)appendTo {
4085 Tab* adjacentTab = nil;
4086 if (appendTo == kCurrentTab)
4087 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354088 [_model insertTabWithURL:URL
4089 referrer:referrer
4090 transition:ui::PAGE_TRANSITION_LINK
4091 opener:adjacentTab
4092 openedByDOM:NO
4093 atIndex:TabModelConstants::kTabPositionAutomatically
4094 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134095}
4096
4097- (void)webPageOrderedOpen:(const GURL&)url
4098 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134099 inIncognito:(BOOL)inIncognito
4100 inBackground:(BOOL)inBackground
4101 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004102 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234103 // feature_engagement::Tracker based on |inIncognito|.
4104 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004105
sdefresnee65fd872016-12-19 13:38:134106 if (inIncognito == _isOffTheRecord) {
4107 [self webPageOrderedOpen:url
4108 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134109 inBackground:inBackground
4110 appendTo:appendTo];
4111 return;
4112 }
4113 // When sending an open command that switches modes, ensure the tab
4114 // ends up appended to the end of the model, not just next to what is
4115 // currently selected in the other mode. This is done with the |append|
4116 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274117 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134118 initWithURL:url
4119 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134120 inIncognito:inIncognito
4121 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274122 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154123 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134124}
4125
4126- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054127 WebStateList* webStateList = [_model webStateList];
4128 webStateList->ReplaceWebStateAt(
4129 webStateList->active_index(),
4130 session_util::CreateWebStateWithNavigationEntries(
4131 [_model browserState], sessionTab->current_navigation_index,
4132 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134133}
4134
4135- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434136 DCHECK(javascript);
4137 javascript = [javascript stringByRemovingPercentEncoding];
4138 web::WebState* webState = [[_model currentTab] webState];
4139 if (webState) {
4140 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4141 }
sdefresnee65fd872016-12-19 13:38:134142}
4143
4144#pragma mark - WebToolbarDelegate methods
4145
4146- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
4147 if (_locationBarHasFocus)
4148 return; // TODO(crbug.com/244366): This should not be necessary.
4149 _locationBarHasFocus = YES;
4150 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524151 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134152 object:nil];
4153 [_sideSwipeController setEnabled:NO];
4154 if ([[_model currentTab].webController wantsKeyboardShield]) {
4155 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4156 [_typingShield setAlpha:0.0];
4157 [_typingShield setHidden:NO];
4158 [UIView animateWithDuration:0.3
4159 animations:^{
4160 [_typingShield setAlpha:1.0];
4161 }];
4162 }
4163 [[OmniboxGeolocationController sharedInstance]
4164 locationBarDidBecomeFirstResponder:_browserState];
4165}
4166
4167- (IBAction)locationBarDidResignFirstResponder:(id)sender {
4168 if (!_locationBarHasFocus)
4169 return; // TODO(crbug.com/244366): This should not be necessary.
4170 _locationBarHasFocus = NO;
4171 [_sideSwipeController setEnabled:YES];
4172 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524173 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134174 object:nil];
4175 [UIView animateWithDuration:0.3
4176 animations:^{
4177 [_typingShield setAlpha:0.0];
4178 }
4179 completion:^(BOOL finished) {
4180 // This can happen if one quickly resigns the omnibox and then taps
4181 // on the omnibox again during this animation. If the animation is
4182 // interrupted and the toolbar controller is first responder, it's safe
4183 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464184 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134185 return;
4186 [_typingShield setHidden:YES];
4187 }];
4188 [[OmniboxGeolocationController sharedInstance]
4189 locationBarDidResignFirstResponder:_browserState];
4190
4191 // If a load was cancelled by an omnibox edit, but nothing is loading when
4192 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4193 if (_locationBarEditCancelledLoad) {
4194 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294195
4196 web::WebState* webState = [_model currentTab].webState;
4197 if (!_toolbarModelIOS->IsLoading() && webState)
4198 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4199 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134200 }
4201}
4202
4203- (IBAction)locationBarBeganEdit:(id)sender {
4204 // On handsets, if a page is currently loading it should be stopped.
4205 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354206 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134207 _locationBarEditCancelledLoad = YES;
4208 }
4209}
4210
sdefresnee65fd872016-12-19 13:38:134211- (ToolbarModelIOS*)toolbarModelIOS {
4212 return _toolbarModelIOS.get();
4213}
4214
sdefresnee65fd872016-12-19 13:38:134215- (void)willUpdateToolbarSnapshot {
4216 [[_model currentTab].overscrollActionsController clear];
4217}
4218
4219- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4220 CGRect frame = [_contentArea frame];
4221 if (!fullScreen) {
4222 // Changing the origin here is unnecessary, it's set in page_animation_util.
4223 frame.size.height -= [self headerHeight];
4224 }
4225
4226 CGFloat shortAxis = frame.size.width;
4227 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524228 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134229 CGFloat aspectRatio = frame.size.height / frame.size.width;
4230 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4231 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4232 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4233 CGRect cardFrame = {frame.origin, cardSize};
4234
4235 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174236 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134237 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4238 : CardCloseButtonSide::LEADING;
4239 [_contentArea addSubview:card];
4240 return card;
4241}
4242
Mark Cogan6ebbde02017-07-07 12:50:134243#pragma mark - BrowserCommands
4244
4245- (void)goBack {
4246 [[_model currentTab] goBack];
4247}
4248
4249- (void)goForward {
4250 [[_model currentTab] goForward];
4251}
4252
Mark Coganb9aac6432017-07-07 13:26:354253- (void)stopLoading {
4254 [_model currentTab].webState->Stop();
4255}
4256
4257- (void)reload {
4258 web::WebState* webState = [_model currentTab].webState;
4259 if (webState) {
4260 // |check_for_repost| is true because the reload is explicitly initiated
4261 // by the user.
4262 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4263 true /* check_for_repost */);
4264 }
4265}
4266
Mark Cogan8e791022017-07-10 09:55:354267- (void)bookmarkPage {
4268 [self initializeBookmarkInteractionController];
4269 [_bookmarkInteractionController
4270 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064271 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354272}
4273
Mark Cogan6acee7f2017-07-11 09:01:404274- (void)showToolsMenu {
4275 DCHECK(_browserState);
4276 DCHECK(self.visible || self.dismissingModal);
4277
4278 // Record the time this menu was requested; to be stored in the configuration
4279 // object.
4280 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4281
4282 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:464283 [_toolbarCoordinator cancelOmniboxEdit];
Mark Cogan6acee7f2017-07-11 09:01:404284 // Dismiss the soft keyboard (if open).
4285 [[_model currentTab].webController dismissKeyboard];
4286 // Dismiss Find in Page focus.
4287 [self updateFindBar:NO shouldFocus:NO];
4288
4289 ToolsMenuConfiguration* configuration =
edchin8e4cfe032017-10-25 13:25:544290 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4291 baseViewController:self];
Mark Cogan6acee7f2017-07-11 09:01:404292 configuration.requestStartTime =
4293 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4294 if ([_model count] == 0)
4295 [configuration setNoOpenedTabs:YES];
4296
4297 if (_isOffTheRecord)
4298 [configuration setInIncognito:YES];
4299
4300 if (!_readingListMenuNotifier) {
4301 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4302 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4303 _browserState)];
4304 }
Cooper Knaake4f495cf2017-07-27 23:30:034305
4306 feature_engagement::Tracker* engagementTracker =
4307 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4308 if (engagementTracker->ShouldTriggerHelpUI(
4309 feature_engagement::kIPHBadgedReadingListFeature)) {
4310 [configuration setShowReadingListNewBadge:YES];
4311 [configuration setEngagementTracker:engagementTracker];
4312 }
Mark Cogan6acee7f2017-07-11 09:01:404313 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4314
4315 [configuration setUserAgentType:self.userAgentType];
4316
Helen Yang9175bd52017-08-12 00:28:404317 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4318 [configuration setHighlightNewIncognitoTabCell:YES];
4319 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4320 }
4321
4322 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4323 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4324 }
4325
sczsf1620e52017-10-02 22:54:464326 [_toolbarCoordinator showToolsMenuPopupWithConfiguration:configuration];
Mark Cogan6acee7f2017-07-11 09:01:404327
4328 ToolsPopupController* toolsPopupController =
sczsf1620e52017-10-02 22:54:464329 [_toolbarCoordinator toolsPopupController];
Mark Cogan6acee7f2017-07-11 09:01:404330 if ([_model currentTab]) {
4331 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4332 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4333 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404334 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4335
4336 if (!IsIPadIdiom())
4337 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4338 }
4339}
4340
Mark Cogandfcdea72017-07-18 13:47:384341- (void)openNewTab:(OpenNewTabCommand*)command {
4342 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144343 // Must take a snapshot of the tab before we switch the incognito mode
4344 // because the currentTab will change after the switch.
4345 Tab* currentTab = [_model currentTab];
4346 if (currentTab) {
4347 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4348 }
Mark Cogandfcdea72017-07-18 13:47:384349 // Not for this browser state, send it on its way.
4350 [self.dispatcher switchModesAndOpenNewTab:command];
4351 return;
4352 }
4353
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004354 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234355 // events to the feature_engagement::Tracker based on |command.userInitiated|
4356 // and |command.incognito|.
4357 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004358
Mark Cogandfcdea72017-07-18 13:47:384359 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4360 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494361 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4362 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384363 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494364 if (oldForegroundTabWasAddedCompletionBlock) {
4365 oldForegroundTabWasAddedCompletionBlock();
4366 }
Mark Cogandfcdea72017-07-18 13:47:384367 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4368 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4369 if (offTheRecord) {
4370 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4371 timeDelta);
4372 } else {
4373 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4374 }
4375 };
4376
4377 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544378 // When the tab switcher presentation experiment is enabled, the new tab can
4379 // be opened before BVC has been made visible onscreen. Test for this case by
4380 // checking if the parent container VC is currently in the process of being
4381 // presented.
4382 DCHECK(self.visible || self.dismissingModal ||
4383 (TabSwitcherPresentsBVCEnabled() &&
4384 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144385
4386 // In most cases, we want to take a snapshot of the current tab before opening
4387 // a new tab. However, if the current tab is not fully visible (did not finish
4388 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4389 // existing snapshot for the tab. This can happen when a new regular tab is
4390 // opened from an incognito tab. A different BVC is displayed, which may not
4391 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384392 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144393 if (currentTab && self.viewVisible) {
Mark Cogandfcdea72017-07-18 13:47:384394 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4395 }
4396 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4397 transition:ui::PAGE_TRANSITION_TYPED];
4398}
4399
Mark Cogan123895002017-07-20 12:54:064400- (void)printTab {
4401 Tab* currentTab = [_model currentTab];
4402 // The UI should prevent users from printing non-printable pages. However, a
4403 // redirection to an un-printable page can happen before it is reflected in
4404 // the UI.
4405 if (![currentTab viewForPrinting]) {
4406 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364407 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064408 return;
4409 }
4410 DCHECK(_browserState);
4411 if (!_printController) {
4412 _printController = [[PrintController alloc]
4413 initWithContextGetter:_browserState->GetRequestContext()];
4414 }
4415 [_printController printView:[currentTab viewForPrinting]
4416 withTitle:[currentTab title]
4417 viewController:self];
4418}
4419
Mark Coganfa25b052017-07-20 17:31:034420- (void)addToReadingList:(ReadingListAddCommand*)command {
4421 [self addToReadingListURL:[command URL] title:[command title]];
4422}
4423
sczs3a8c8602017-08-01 20:14:084424- (void)showReadingList {
4425 _readingListCoordinator = [[ReadingListCoordinator alloc]
4426 initWithBaseViewController:self
4427 browserState:self.browserState
4428 loader:self];
4429
4430 [_readingListCoordinator start];
4431}
4432
Jean-François Geyelinedef9552017-08-07 09:56:564433- (void)preloadVoiceSearch {
4434 // Preload VoiceSearchController and views and view controllers needed
4435 // for voice search.
4436 [self ensureVoiceSearchControllerCreated];
4437 _voiceSearchController->PrepareToAppear();
4438}
4439
edchinc5720722017-08-14 22:06:314440#if !defined(NDEBUG)
4441- (void)viewSource {
4442 Tab* tab = [_model currentTab];
4443 DCHECK(tab);
4444 CRWWebController* webController = tab.webController;
4445 NSString* script = @"document.documentElement.outerHTML;";
4446 __weak Tab* weakTab = tab;
4447 __weak BrowserViewController* weakSelf = self;
4448 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4449 Tab* strongTab = weakTab;
4450 if (!strongTab)
4451 return;
4452 if (![result isKindOfClass:[NSString class]])
4453 result = @"Not an HTML page";
4454 std::string base64HTML;
4455 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4456 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194457 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314458 web::ReferrerPolicyDefault);
4459
4460 [[weakSelf tabModel]
4461 insertTabWithURL:URL
4462 referrer:referrer
4463 transition:ui::PAGE_TRANSITION_LINK
4464 opener:strongTab
4465 openedByDOM:YES
4466 atIndex:TabModelConstants::kTabPositionAutomatically
4467 inBackground:NO];
4468 };
4469 [webController executeJavaScript:script
4470 completionHandler:completionHandlerBlock];
4471}
4472#endif // !defined(NDEBUG)
4473
edchin2134c042017-08-18 13:57:354474// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4475- (void)showRateThisAppDialog {
4476 DCHECK(!_rateThisAppDialog);
4477
4478 // Store the current timestamp whenever this dialog is shown.
4479 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4480 base::Time::Now().ToInternalValue());
4481
Gregory Chatzinofff39ec5162017-10-05 20:28:534482 // iOS11 no longer supports the itms link to the app store. So, use a deep
4483 // link for iOS11 and the itms link for prior versions.
4484 NSURL* storeURL;
4485 if (base::ios::IsRunningOnIOS11OrLater()) {
4486 storeURL =
4487 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4488 @"google-chrome-the-fast-and-secure-web-browser/"
4489 @"id535886823?action=write-review")];
4490 } else {
4491 storeURL = [NSURL
4492 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4493 @"MZStore.woa/wa/"
4494 @"viewContentsUserReviews?type=Purple+Software&id="
4495 @"535886823&pt=9008&ct=rating")];
4496 }
edchin2134c042017-08-18 13:57:354497
4498 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014499 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354500
4501 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4502 [_rateThisAppDialog setAppStoreURL:storeURL];
4503 [_rateThisAppDialog setDelegate:self];
4504 [_rateThisAppDialog show];
4505}
4506
Gregory Chatzinoff3f40c1542017-08-30 07:50:044507- (void)showFindInPage {
4508 if (!self.canShowFindBar)
4509 return;
4510
4511 if (!_findBarController) {
4512 _findBarController =
4513 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4514 _findBarController.dispatcher = self.dispatcher;
4515 }
4516
4517 Tab* tab = [_model currentTab];
4518 DCHECK(tab);
4519 auto* helper = FindTabHelper::FromWebState(tab.webState);
4520 DCHECK(!helper->IsFindUIActive());
4521 helper->SetFindUIActive(true);
4522 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4523}
4524
4525- (void)closeFindInPage {
4526 __weak BrowserViewController* weakSelf = self;
4527 Tab* currentTab = [_model currentTab];
4528 if (currentTab) {
4529 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4530 [weakSelf updateFindBar:NO shouldFocus:NO];
4531 });
4532 }
4533}
4534
4535- (void)searchFindInPage {
4536 DCHECK([_model currentTab]);
4537 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4538 __weak BrowserViewController* weakSelf = self;
4539 helper->StartFinding(
4540 [_findBarController searchTerm], ^(FindInPageModel* model) {
4541 BrowserViewController* strongSelf = weakSelf;
4542 if (!strongSelf) {
4543 return;
4544 }
4545 [strongSelf->_findBarController updateResultsCount:model];
4546 });
4547
4548 if (!_isOffTheRecord)
4549 helper->PersistSearchTerm();
4550}
4551
4552- (void)findNextStringInPage {
4553 Tab* currentTab = [_model currentTab];
4554 DCHECK(currentTab);
4555 // TODO(crbug.com/603524): Reshow find bar if necessary.
4556 FindTabHelper::FromWebState(currentTab.webState)
4557 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4558 [_findBarController updateResultsCount:model];
4559 });
4560}
4561
4562- (void)findPreviousStringInPage {
4563 Tab* currentTab = [_model currentTab];
4564 DCHECK(currentTab);
4565 // TODO(crbug.com/603524): Reshow find bar if necessary.
4566 FindTabHelper::FromWebState(currentTab.webState)
4567 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4568 [_findBarController updateResultsCount:model];
4569 });
4570}
4571
edchinf84b2502017-08-31 21:30:454572- (void)showHelpPage {
4573 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4574 [self webPageOrderedOpen:helpUrl
4575 referrer:web::Referrer()
4576 inBackground:NO
4577 appendTo:kCurrentTab];
4578}
4579
edchinb59b5602017-09-01 15:00:204580- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104581 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204582 [self showAllBookmarks];
4583 } else {
4584 [self initializeBookmarkInteractionController];
4585 [_bookmarkInteractionController presentBookmarks];
4586 }
4587}
4588
edchin8ee0807d2017-09-01 23:52:474589- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104590 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474591 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4592 } else {
4593 if (!self.recentTabsCoordinator) {
4594 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4595 initWithBaseViewController:self];
4596 self.recentTabsCoordinator.loader = self;
4597 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4598 self.recentTabsCoordinator.browserState = _browserState;
4599 }
4600 [self.recentTabsCoordinator start];
4601 }
4602}
4603
Mark Cogan6de7e9a2017-09-06 12:57:214604- (void)requestDesktopSite {
4605 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4606}
4607
4608- (void)requestMobileSite {
4609 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4610}
4611
sdefresnee65fd872016-12-19 13:38:134612#pragma mark - Command Handling
4613
sdefresnee65fd872016-12-19 13:38:134614- (void)closeCurrentTab {
4615 Tab* currentTab = [_model currentTab];
4616 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4617 if (tabIndex == NSNotFound)
4618 return;
4619
jif7fed8122017-02-08 13:15:254620 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4621 // iPad.
sdefresnee65fd872016-12-19 13:38:134622 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4623 exitingPage.image =
4624 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4625
4626 // Close the actual tab, and add its image as a subview.
4627 [_model closeTabAtIndex:tabIndex];
4628
4629 // Do not animate close in iPad.
4630 if (!IsIPadIdiom()) {
4631 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524632 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134633 exitingPage, 0, YES, IsPortrait(), ^{
4634 [exitingPage removeFromSuperview];
4635 });
4636 }
4637}
4638
Elodie Banelaa5ab432017-09-28 14:42:014639- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4640 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434641 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134642 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4643 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014644 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464645 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014646 }
sdefresnee65fd872016-12-19 13:38:134647 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274648 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474649 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134650 if (_voiceSearchController)
4651 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364652
4653 Tab* currentTab = [_model currentTab];
4654 [currentTab dismissModals];
4655
rohitrao005a6432017-03-16 20:52:424656 if (currentTab) {
4657 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4658 if (findHelper) {
4659 findHelper->StopFinding(^{
4660 [self updateFindBar:NO shouldFocus:NO];
4661 });
4662 }
4663 }
rohitraob2bf3cb2017-02-10 14:10:364664
sdefresnee65fd872016-12-19 13:38:134665 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134666 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274667 _printController = nil;
sczsf1620e52017-10-02 22:54:464668 [_toolbarCoordinator dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134669 [_contextMenuCoordinator stop];
4670 [self dismissRateThisAppDialog];
4671
4672 if (self.presentedViewController) {
4673 // Dismisses any other modal controllers that may be present, e.g. Recent
4674 // Tabs.
Rohit Raoa1f1bac2017-11-07 16:27:514675 //
sdefresnee65fd872016-12-19 13:38:134676 // Note that currently, some controllers like the bookmark ones were already
4677 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
Rohit Raoa1f1bac2017-11-07 16:27:514678 // but are still reported as the presentedViewController. Calling
4679 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
4680 // itself, so instead check the value of |self.dismissingModal| and only
4681 // call dismiss if one of the above calls has not already triggered a
4682 // dismissal.
4683 //
4684 // To ensure the completion is called, nil is passed to the call to dismiss,
4685 // and the completion is called explicitly below.
4686 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
4687 [self dismissViewControllerAnimated:NO completion:nil];
4688 }
sdefresnee65fd872016-12-19 13:38:134689 // Dismissed controllers will be so after a delay. Queue the completion
4690 // callback after that.
4691 if (completion) {
4692 dispatch_after(
4693 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4694 dispatch_get_main_queue(), ^{
4695 completion();
4696 });
4697 }
4698 } else if (completion) {
4699 // If no view controllers are presented, we should be ok with dispatching
4700 // the completion block directly.
4701 dispatch_async(dispatch_get_main_queue(), completion);
4702 }
4703}
4704
sdefresnee65fd872016-12-19 13:38:134705#pragma mark - Find Bar
4706
4707- (void)hideFindBarWithAnimation:(BOOL)animate {
4708 [_findBarController hideFindBarView:animate];
4709}
4710
4711- (void)showFindBarWithAnimation:(BOOL)animate
4712 selectText:(BOOL)selectText
4713 shouldFocus:(BOOL)shouldFocus {
4714 DCHECK(_findBarController);
4715 Tab* tab = [_model currentTab];
4716 DCHECK(tab);
4717 CRWWebController* webController = tab.webController;
4718
4719 CGRect referenceFrame = CGRectZero;
4720 if (IsIPadIdiom()) {
4721 referenceFrame = webController.visibleFrame;
4722 referenceFrame.origin.y -= kIPadFindBarOverlap;
4723 } else {
4724 referenceFrame = _contentArea.frame;
4725 }
4726
sczsf1620e52017-10-02 22:54:464727 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134728 [_findBarController addFindBarView:animate
4729 intoView:self.view
4730 withFrame:referenceFrame
4731 alignWithFrame:omniboxFrame
4732 selectText:selectText];
4733 [self updateFindBar:YES shouldFocus:shouldFocus];
4734}
4735
sdefresnee65fd872016-12-19 13:38:134736- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324737 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4738 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4739 // in a crash.
4740 if (![_model currentTab]) {
4741 return;
4742 }
rohitrao005a6432017-03-16 20:52:424743 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4744 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134745 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424746 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134747 }
4748
4749 [self setFramesForHeaders:[self headerViews]
4750 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424751 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134752 initialUpdate:initialUpdate
4753 focusTextfield:shouldFocus];
4754 } else {
4755 [self hideFindBarWithAnimation:YES];
4756 }
4757}
4758
4759- (void)showAllBookmarks {
4760 DCHECK(self.visible || self.dismissingModal);
4761 GURL URL(kChromeUIBookmarksURL);
4762 Tab* tab = [_model currentTab];
4763 web::NavigationManager::WebLoadParams params(URL);
4764 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234765 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134766}
4767
Gauthier Ambardf520c022017-08-29 07:42:234768- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134769 DCHECK(self.visible || self.dismissingModal);
4770 GURL url(kChromeUINewTabURL);
4771 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4772 if (fragment != "") {
4773 GURL::Replacements replacement;
4774 replacement.SetRefStr(fragment);
4775 url = url.ReplaceComponents(replacement);
4776 }
4777 Tab* tab = [_model currentTab];
4778 web::NavigationManager::WebLoadParams params(url);
4779 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234780 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134781}
4782
sdefresnee65fd872016-12-19 13:38:134783- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274784 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134785 base::RecordAction(base::UserMetricsAction(
4786 "IOSRateThisAppDialogDismissedProgramatically"));
4787 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274788 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134789 }
4790}
4791
Jean-François Geyelin5d2e184c2017-07-28 19:48:004792- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4793 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134794 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124795 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134796 _startVoiceSearchAfterNewTabAnimation = YES;
4797 return;
4798 }
4799
4800 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4801 // dismiss the keyboard.
4802 [self closeFindInPage];
4803 [[_model currentTab].webController dismissKeyboard];
4804
4805 // Ensure that voice search objects are created.
4806 [self ensureVoiceSearchControllerCreated];
4807 [self ensureVoiceSearchBarCreated];
4808
4809 // Present voice search.
4810 [_voiceSearchBar prepareToPresentVoiceSearch];
4811 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:464812 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:134813}
4814
4815#pragma mark - ToolbarOwner
4816
4817- (ToolbarController*)relinquishedToolbarController {
4818 if (_isToolbarControllerRelinquished)
4819 return nil;
4820
4821 ToolbarController* relinquishedToolbarController = nil;
sczs42f7f7482017-11-08 01:13:274822 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
sdefresnee65fd872016-12-19 13:38:134823 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:194824 if (currentTab.webState &&
4825 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
sdefresnee65fd872016-12-19 13:38:134826 // Use the native content controller's toolbar when the BVC's is hidden.
4827 id nativeController = [self nativeControllerForTab:currentTab];
4828 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4829 relinquishedToolbarController =
4830 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274831 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134832 }
4833 }
4834 } else {
sczs42f7f7482017-11-08 01:13:274835 relinquishedToolbarController = _toolbarCoordinator.webToolbarController;
4836 [_toolbarCoordinator.toolbarViewController
4837 willMoveToParentViewController:nil];
4838 [_toolbarCoordinator.toolbarViewController.view removeFromSuperview];
4839 [_toolbarCoordinator.toolbarViewController removeFromParentViewController];
sdefresnee65fd872016-12-19 13:38:134840 }
4841 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4842 return relinquishedToolbarController;
4843}
4844
4845- (void)reparentToolbarController {
4846 if (_isToolbarControllerRelinquished) {
sczs42f7f7482017-11-08 01:13:274847 if ([_toolbarCoordinator.toolbarViewController.view
4848 isDescendantOfView:self.view]) {
sdefresnee65fd872016-12-19 13:38:134849 // A native content controller's toolbar has been relinquished.
4850 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274851 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134852 } else if ([_findBarController isFindInPageShown]) {
sczs42f7f7482017-11-08 01:13:274853 [self.view insertSubview:_toolbarCoordinator.toolbarViewController.view
sdefresnee65fd872016-12-19 13:38:134854 belowSubview:[_findBarController view]];
Justin Cohenba27610e2017-11-08 19:34:454855 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:504856 [self addConstraintsToToolbar];
4857 }
sdefresnee65fd872016-12-19 13:38:134858 } else {
sczs42f7f7482017-11-08 01:13:274859 [self.view addSubview:_toolbarCoordinator.toolbarViewController.view];
Justin Cohenba27610e2017-11-08 19:34:454860 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:504861 [self addConstraintsToToolbar];
4862 }
sdefresnee65fd872016-12-19 13:38:134863 }
sdefresnee65fd872016-12-19 13:38:134864 _isToolbarControllerRelinquished = NO;
4865 }
4866}
4867
Gauthier Ambard04ddb512017-11-07 09:14:164868- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:274869 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164870}
4871
Gauthier Ambard996d9b12017-11-06 09:39:214872- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4873 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:274874 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214875 Tab* currentTab = [_model currentTab];
4876 if (currentTab.webState &&
4877 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4878 // Use the native content controller's toolbar when the BVC's is hidden.
4879 id nativeController = [self nativeControllerForTab:currentTab];
4880 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4881 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4882 }
4883 }
4884 } else {
4885 toolbarSnapshotProvider = _toolbarCoordinator;
4886 }
4887 return toolbarSnapshotProvider;
4888}
4889
sdefresnee65fd872016-12-19 13:38:134890#pragma mark - TabModelObserver methods
4891
4892// Observer method, tab inserted.
4893- (void)tabModel:(TabModel*)model
4894 didInsertTab:(Tab*)tab
4895 atIndex:(NSUInteger)modelIndex
4896 inForeground:(BOOL)fg {
4897 DCHECK(tab);
4898 [self installDelegatesForTab:tab];
4899
4900 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194901 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134902 }
4903}
4904
4905// Observer method, active tab changed.
4906- (void)tabModel:(TabModel*)model
4907 didChangeActiveTab:(Tab*)newTab
4908 previousTab:(Tab*)previousTab
4909 atIndex:(NSUInteger)index {
4910 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4911 // Currently this observer method is always called with a non-nil |newTab|,
4912 // but that may change in the future. Remove this DCHECK when it does.
4913 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274914 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304915 DCHECK(newTab.webState);
4916 infobars::InfoBarManager* infoBarManager =
4917 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134918 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4919 }
4920 [self updateVoiceSearchBarVisibilityAnimated:NO];
4921
Mohamad Ahmadi7d09ec32017-07-11 22:32:194922 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134923
4924 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134925}
4926
4927// Observer method, tab changed.
4928- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4929 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4930 if (tab == [_model currentTab]) {
4931 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134932 }
4933}
4934
sdefresne49cf2862017-03-15 13:46:144935// Observer method, tab replaced.
4936- (void)tabModel:(TabModel*)model
4937 didReplaceTab:(Tab*)oldTab
4938 withTab:(Tab*)newTab
4939 atIndex:(NSUInteger)index {
4940 [self uninstallDelegatesForTab:oldTab];
4941 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264942
michaeldo79909fb2017-05-09 23:42:504943 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304944 infobars::InfoBarManager* infoBarManager = nullptr;
4945 if (newTab) {
4946 DCHECK(newTab.webState);
4947 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4948 }
michaeldo79909fb2017-05-09 23:42:504949 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4950 }
4951
kkhorimotofa0844cc2017-03-20 17:01:264952 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4953 if (self.active && model.currentTab == newTab)
4954 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464955
4956 if (newTab)
4957 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144958}
4959
sdefresnee65fd872016-12-19 13:38:134960// A tab has been removed, remove its views from display if necessary.
4961- (void)tabModel:(TabModel*)model
4962 didRemoveTab:(Tab*)tab
4963 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144964 [self uninstallDelegatesForTab:tab];
4965
kkhorimoto496fdd72017-06-12 19:56:314966 // Cancel dialogs for |tab|'s WebState.
4967 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4968
sdefresnee65fd872016-12-19 13:38:134969 // Ignore changes while the tab stack view is visible (or while suspended).
4970 // The display will be refreshed when this view becomes active again.
4971 if (!self.visible || !model.webUsageEnabled)
4972 return;
4973
4974 // Remove the find bar for now.
4975 [self hideFindBarWithAnimation:NO];
4976}
4977
4978- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4979 if (tab == [model currentTab]) {
4980 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464981 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134982 }
4983
Mohamad Ahmadi7d09ec32017-07-11 22:32:194984 [_paymentRequestManager stopTrackingWebState:tab.webState];
4985
sdefresnee65fd872016-12-19 13:38:134986 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4987 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194988 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134989 }
4990}
4991
4992// Called when the number of tabs changes. Update the toolbar accordingly.
4993- (void)tabModelDidChangeTabCount:(TabModel*)model {
4994 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464995 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134996}
4997
4998#pragma mark - Upgrade Detection
4999
5000- (void)showUpgrade:(UpgradeCenter*)center {
5001 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:275002 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:135003 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:305004 DCHECK(tab.webState);
5005 infobars::InfoBarManager* infoBarManager =
5006 InfoBarManagerImpl::FromWebState(tab.webState);
5007 DCHECK(infoBarManager);
5008 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:135009 }
5010}
5011
sdefresnee65fd872016-12-19 13:38:135012
5013#pragma mark - InfoBarControllerDelegate
5014
5015- (void)infoBarContainerStateChanged:(bool)isAnimating {
5016 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
5017 DCHECK(infoBarContainerView);
5018 CGRect containerFrame = infoBarContainerView.frame;
5019 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
5020 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
5021 containerFrame.size.height = height;
5022 BOOL isViewVisible = self.visible;
5023 [UIView animateWithDuration:0.1
5024 animations:^{
5025 [infoBarContainerView setFrame:containerFrame];
5026 }
5027 completion:^(BOOL finished) {
5028 if (!isViewVisible)
5029 return;
5030 UIAccessibilityPostNotification(
5031 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
5032 }];
5033}
5034
5035- (BOOL)shouldAutorotate {
5036 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
5037 // Don't rotate if a voice search is being presented or dismissed. Once the
5038 // transition animations finish, only the Voice Search UIViewController's
5039 // |-shouldAutorotate| will be called.
5040 return NO;
5041 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
5042 // Don't auto rotate if side swipe controller view says not to.
5043 return NO;
5044 } else {
5045 return [super shouldAutorotate];
5046 }
5047}
5048
5049// Always return yes, as this tap should work with various recognizers,
5050// including UITextTapRecognizer, UILongPressGestureRecognizer,
5051// UIScrollViewPanGestureRecognizer and others.
5052- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
5053 shouldRecognizeSimultaneouslyWithGestureRecognizer:
5054 (UIGestureRecognizer*)otherGestureRecognizer {
5055 return YES;
5056}
5057
5058// Tap gestures should only be recognized within |_contentArea|.
5059- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
5060 CGPoint location = [gesture locationInView:self.view];
5061
5062 // Only allow touches on descendant views of |_contentArea|.
5063 UIView* hitView = [self.view hitTest:location withEvent:nil];
5064 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
5065}
5066
5067#pragma mark - SideSwipeController Delegate Methods
5068
5069- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
5070 DCHECK(!IsIPadIdiom());
5071 // Update frame incase orientation changed while |_contentArea| was out of
5072 // the view hierarchy.
5073 [_contentArea setFrame:[sideSwipeView frame]];
5074
Justin Cohen16ad60e2017-11-10 14:56:265075 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135076 [self updateVoiceSearchBarVisibilityAnimated:NO];
5077 [self updateToolbar];
5078
5079 // Reset horizontal stack view.
5080 [sideSwipeView removeFromSuperview];
5081 [_sideSwipeController setInSwipe:NO];
5082 [_infoBarContainer->view() setHidden:NO];
5083}
5084
5085- (UIView*)contentView {
5086 return _contentArea;
5087}
5088
sdefresnee65fd872016-12-19 13:38:135089- (BOOL)preventSideSwipe {
sczsf1620e52017-10-02 22:54:465090 if ([_toolbarCoordinator toolsPopupController])
sdefresnee65fd872016-12-19 13:38:135091 return YES;
5092
5093 if (_voiceSearchController && _voiceSearchController->IsVisible())
5094 return YES;
5095
sdefresnee65fd872016-12-19 13:38:135096 if (!self.active)
5097 return YES;
5098
5099 return NO;
5100}
5101
5102- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5103 if (visible) {
5104 [self updateVoiceSearchBarVisibilityAnimated:NO];
5105 [self updateToolbar];
5106 [_infoBarContainer->view() setHidden:NO];
5107 } else {
5108 // Hide UI accessories such as find bar and first visit overlays
5109 // for welcome page.
5110 [self hideFindBarWithAnimation:NO];
5111 [_infoBarContainer->view() setHidden:YES];
5112 [_voiceSearchBar setHidden:YES];
5113 }
5114}
5115
5116- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5117 BOOL seenToolbar = NO;
5118 BOOL seenInfoBarContainer = NO;
5119 BOOL seenContentArea = NO;
5120 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275121 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135122 seenToolbar = YES;
5123 else if (view == _infoBarContainer->view())
5124 seenInfoBarContainer = YES;
5125 else if (view == _contentArea)
5126 seenContentArea = YES;
5127 if ((seenToolbar && !seenInfoBarContainer) ||
5128 (seenInfoBarContainer && !seenContentArea))
5129 return NO;
5130 }
5131 return YES;
5132}
5133
5134#pragma mark - PreloadControllerDelegate methods
5135
rohitraoeeb5293b2017-06-15 14:40:025136- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445137 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135138}
5139
rohitraoeeb5293b2017-06-15 14:40:025140- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5141 return [self hasControllerForURL:url];
5142}
5143
sdefresnee65fd872016-12-19 13:38:135144#pragma mark - BookmarkBridgeMethods
5145
5146// If an added or removed bookmark is the same as the current url, update the
5147// toolbar so the star highlight is kept in sync.
5148- (void)bookmarkNodeModified:(const BookmarkNode*)node {
Sylvain Defresnee7f2c8a2017-10-17 02:39:195149 if ([_model currentTab].webState &&
5150 node->url() == [_model currentTab].webState->GetLastCommittedURL()) {
sdefresnee65fd872016-12-19 13:38:135151 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255152 }
sdefresnee65fd872016-12-19 13:38:135153}
5154
5155// If all bookmarks are removed, update the toolbar so the star highlight is
5156// kept in sync.
5157- (void)allBookmarksRemoved {
5158 [self updateToolbar];
5159}
5160
sdefresnee65fd872016-12-19 13:38:135161- (void)showErrorAlertWithStringTitle:(NSString*)title
5162 message:(NSString*)message {
5163 // Dismiss current alert.
5164 [_alertCoordinator stop];
5165
stkhapuginc9eee7b2017-04-10 15:49:275166 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5167 message:message
5168 viewController:self];
sdefresnee65fd872016-12-19 13:38:135169 [_alertCoordinator start];
5170}
5171
edchineeb4d422017-10-02 17:39:365172- (void)showSnackbar:(NSString*)text {
5173 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5174 message.accessibilityLabel = text;
5175 message.duration = 2.0;
5176 message.category = kBrowserViewControllerSnackbarCategory;
5177 [self.dispatcher showSnackbarMessage:message];
5178}
5179
sdefresnee65fd872016-12-19 13:38:135180#pragma mark - Show Mail Composer methods
5181
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575182- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5183 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135184 if (![MFMailComposeViewController canSendMail]) {
5185 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575186 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135187 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575188 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135189 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5190 return;
5191 }
stkhapuginc9eee7b2017-04-10 15:49:275192 MFMailComposeViewController* mailViewController =
5193 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135194 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575195 [mailViewController setToRecipients:[context toRecipients]];
5196 [mailViewController setSubject:[context subject]];
5197 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135198
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575199 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135200 if (!textFile.empty()) {
5201 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5202 NSData* data = [NSData dataWithContentsOfFile:filename];
5203 if (data) {
5204 NSString* displayName =
5205 base::SysUTF8ToNSString(textFile.BaseName().value());
5206 [mailViewController addAttachmentData:data
5207 mimeType:@"text/plain"
5208 fileName:displayName];
5209 }
5210 }
5211
5212 [mailViewController setMailComposeDelegate:self];
5213 [self presentViewController:mailViewController animated:YES completion:nil];
5214}
5215
5216#pragma mark - MFMailComposeViewControllerDelegate methods
5217
5218- (void)mailComposeController:(MFMailComposeViewController*)controller
5219 didFinishWithResult:(MFMailComposeResult)result
5220 error:(NSError*)error {
5221 [self dismissViewControllerAnimated:YES completion:nil];
5222}
5223
5224#pragma mark - StoreKitLauncher methods
5225
5226- (void)productViewControllerDidFinish:
5227 (SKStoreProductViewController*)viewController {
5228 [self dismissViewControllerAnimated:YES completion:nil];
5229}
5230
5231- (void)openAppStore:(NSString*)appId {
5232 if (![appId length])
5233 return;
5234 NSDictionary* product =
5235 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275236 SKStoreProductViewController* storeViewController =
5237 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135238 [storeViewController setDelegate:self];
5239 [storeViewController loadProductWithParameters:product completionBlock:nil];
5240 [self presentViewController:storeViewController animated:YES completion:nil];
5241}
5242
5243#pragma mark - TabDialogDelegate methods
5244
sdefresnee65fd872016-12-19 13:38:135245- (void)cancelDialogForTab:(Tab*)tab {
5246 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5247}
5248
5249#pragma mark - FKFeedbackPromptDelegate methods
5250
5251- (void)userTappedRateApp:(UIView*)view {
5252 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275253 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135254}
5255
5256- (void)userTappedSendFeedback:(UIView*)view {
5257 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275258 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205259 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135260}
5261
5262- (void)userTappedDismiss:(UIView*)view {
5263 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275264 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135265}
5266
5267#pragma mark - VoiceSearchBarDelegate
5268
5269- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275270 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135271 [self ensureVoiceSearchControllerCreated];
5272 return _voiceSearchController->IsTextToSpeechEnabled() &&
5273 _voiceSearchController->IsTextToSpeechSupported();
5274}
5275
5276- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275277 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135278 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5279}
5280
5281#pragma mark - VoiceSearchPresenter
5282
5283- (UIView*)voiceSearchButton {
5284 return _voiceSearchButton;
5285}
5286
5287- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5288 return [self currentLogoAnimationControllerOwner];
5289}
5290
Rohit Rao01e0e002017-08-14 20:49:435291#pragma mark - ActivityService Providers
5292
5293- (void)presentActivityServiceViewController:(UIViewController*)controller {
5294 [self presentViewController:controller animated:YES completion:nil];
5295}
5296
5297- (void)activityServiceDidEndPresenting {
5298 self.presenting = NO;
5299 [self.dialogPresenter tryToPresent];
5300}
5301
Rohit Raocda0a992017-08-16 15:37:115302#pragma mark - QRScanner Requirements
5303
5304- (void)presentQRScannerViewController:(UIViewController*)controller {
5305 [self presentViewController:controller animated:YES completion:nil];
5306}
5307
5308- (void)dismissQRScannerViewController:(UIViewController*)controller
5309 completion:(void (^)(void))completion {
5310 DCHECK_EQ(controller, self.presentedViewController);
5311 [self dismissViewControllerAnimated:YES completion:completion];
5312}
5313
sczsdd860eba2017-08-10 01:55:385314#pragma mark - TabHistoryPresenter
5315
sczs0a726d22017-08-21 22:40:135316- (UIView*)viewForTabHistoryPresentation {
5317 return self.view;
5318}
5319
sczsdd860eba2017-08-10 01:55:385320- (void)prepareForTabHistoryPresentation {
5321 DCHECK(self.visible || self.dismissingModal);
5322 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465323 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385324}
5325
Mike Doughertya1ec26402017-08-23 19:46:315326#pragma mark - CaptivePortalDetectorTabHelperDelegate
5327
Mike Dougherty4620cf8e2017-10-31 23:37:095328- (void)captivePortalDetectorTabHelper:
5329 (CaptivePortalDetectorTabHelper*)tabHelper
5330 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285331 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315332}
5333
Gregory Chatzinoffdf93d692017-09-09 01:32:275334#pragma mark - PageInfoPresentation
5335
Gregory Chatzinoffb6a01f72017-09-20 20:06:395336- (void)presentPageInfoView:(UIView*)pageInfoView {
5337 [pageInfoView setFrame:self.view.bounds];
5338 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275339}
5340
5341- (void)prepareForPageInfoPresentation {
5342 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465343 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275344}
5345
Gregory Chatzinoffb6a01f72017-09-20 20:06:395346- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5347 return [self.view convertPoint:origin fromView:nil];
5348}
5349
Sylvain Defresnecacc3a52017-09-12 13:51:045350#pragma mark - WebStatePrinter
5351
5352- (void)printWebState:(web::WebState*)webState {
5353 if (webState == [_model currentTab].webState)
5354 [self printTab];
5355}
5356
Eugene But35ded552017-09-13 23:31:595357#pragma mark - RepostFormTabHelperDelegate
5358
5359- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
5360 presentRepostFromDialogAtPoint:(CGPoint)location
5361 completionHandler:(void (^)(BOOL))completion {
5362 _repostFormCoordinator = [[RepostFormCoordinator alloc]
5363 initWithBaseViewController:self
5364 dialogLocation:location
5365 webState:helper->web_state()
5366 completionHandler:completion];
5367 [_repostFormCoordinator start];
5368}
5369
5370- (void)repostFormTabHelperDismissRepostFormDialog:
5371 (RepostFormTabHelper*)helper {
5372 _repostFormCoordinator = nil;
5373}
5374
edchinf5150c682017-09-18 02:50:035375#pragma mark - TabStripPresentation
5376
5377- (BOOL)isTabStripFullyVisible {
5378 return ([self currentHeaderOffset] == 0.0f);
5379}
5380
5381- (void)showTabStripView:(UIView*)tabStripView {
5382 DCHECK([self isViewLoaded]);
5383 DCHECK(tabStripView);
5384 self.tabStripView = tabStripView;
5385 CGRect tabStripFrame = [self.tabStripView frame];
5386 tabStripFrame.origin = CGPointZero;
5387 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5388 // because the CGPointZero above will break reset the offset, but it's not
5389 // clear what removing that will do.
5390 tabStripFrame.origin.y = [self headerOffset];
5391 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5392 [self.tabStripView setFrame:tabStripFrame];
5393 [[self view] addSubview:tabStripView];
5394}
5395
edchincd32fdf2017-10-25 12:45:455396#pragma mark - ManageAccountsDelegate
5397
5398- (void)onManageAccounts {
5399 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5400 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5401 ->GetState());
edchin5b8aa052017-10-30 23:27:285402 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455403}
5404
5405- (void)onAddAccount {
5406 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5407 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5408 ->GetState());
edchinb082b2982017-11-03 19:41:215409 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455410}
5411
5412- (void)onGoIncognito:(const GURL&)url {
5413 // The user taps on go incognito from the mobile U-turn webpage (the web page
5414 // that displays all users accounts available in the content area). As the
5415 // user chooses to go to incognito, the mobile U-turn page is no longer
5416 // neeeded. The current solution is to go back in history. This has the
5417 // advantage of keeping the current browsing session and give a good user
5418 // experience when the user comes back from incognito.
5419 [self.tabModel.currentTab goBack];
5420
5421 if (url.is_valid()) {
5422 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5423 initWithURL:url
5424 referrer:web::Referrer() // Strip referrer when switching modes.
5425 inIncognito:YES
5426 inBackground:NO
5427 appendTo:kLastTab];
5428 [self.dispatcher openURL:command];
5429 } else {
5430 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5431 }
5432}
5433
edchin95c927072017-11-04 00:35:075434#pragma mark - SyncPresenter
5435
5436- (void)showReauthenticateSignin {
5437 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125438 showSignin:
5439 [[ShowSigninCommand alloc]
5440 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5441 accessPoint:signin_metrics::AccessPoint::
5442 ACCESS_POINT_UNKNOWN]
5443 baseViewController:self];
edchin95c927072017-11-04 00:35:075444}
5445
5446- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505447 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075448}
5449
5450- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545451 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075452}
5453
edchin9e7a1112017-11-07 18:28:035454#pragma mark - SigninPresenter
5455
5456- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125457 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035458}
5459
sdefresnee65fd872016-12-19 13:38:135460@end