blob: ed4a6aabca4856a550d06fb7690f9569f2512412 [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 Doughertya1ec26402017-08-23 19:46:3199#import "ios/chrome/browser/ssl/ios_captive_portal_blocking_page_delegate.h"
pkld6e73e52017-03-08 15:56:51100#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28101#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13102#import "ios/chrome/browser/tabs/tab.h"
103#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19104#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13105#import "ios/chrome/browser/tabs/tab_model.h"
106#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16107#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13108#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Rohit Rao01e0e002017-08-14 20:49:43109#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
110#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13111#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59112#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13113#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
114#import "ios/chrome/browser/ui/background_generator.h"
115#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
116#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13117#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38118#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
Mike Doughertya1ec26402017-08-23 19:46:31119#import "ios/chrome/browser/ui/captive_portal/captive_portal_login_coordinator.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"
edchin7f210cd2017-09-28 08:03:53127#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00128#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23129#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13130#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13131#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
132#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
133#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
134#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35135#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13136#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
137#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimoto1945ef42017-10-26 03:57:26138#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
Kurt Horimoto803840622017-10-28 01:20:37139#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
sczsdd860eba2017-08-10 01:55:38140#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13141#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13142#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26143#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59144#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10145#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13146#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42147#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13148#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27149#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
150#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13151#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37152#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13153#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11154#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
155#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13156#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03157#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13158#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13159#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53160#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39161#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13162#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin7f210cd2017-09-28 08:03:53163#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13164#import "ios/chrome/browser/ui/stack_view/card_view.h"
165#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
166#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13167#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03168#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
169#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
170#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
sczs6ca1d262017-10-30 20:27:13171#import "ios/chrome/browser/ui/toolbar/public/toolbar_controller_base_feature.h"
sczsf1620e52017-10-02 22:54:46172#include "ios/chrome/browser/ui/toolbar/toolbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13173#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
174#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31175#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
sczsc2b4f152017-10-11 01:44:24176#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
sczsbbad1632017-07-29 03:48:00177#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13178#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
179#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
180#include "ios/chrome/browser/ui/ui_util.h"
181#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28182#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13183#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
184#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51185#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13186#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27187#import "ios/chrome/browser/web/load_timing_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13188#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04189#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05190#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59191#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53192#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04193#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05194#import "ios/chrome/browser/web_state_list/web_state_list.h"
195#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57196#import "ios/chrome/browser/webui/net_export_tab_helper.h"
197#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
198#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13199#include "ios/chrome/grit/ios_chromium_strings.h"
200#include "ios/chrome/grit/ios_strings.h"
201#import "ios/net/request_tracker.h"
202#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
203#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
204#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
205#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
206#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
207#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
208#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
209#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36210#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13211#include "ios/web/public/navigation_item.h"
212#import "ios/web/public/navigation_manager.h"
213#include "ios/web/public/referrer_util.h"
214#include "ios/web/public/ssl_status.h"
215#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39216#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13217#include "ios/web/public/web_client.h"
218#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13219#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29220#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19221#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13222#import "ios/web/public/web_state/web_state_delegate_bridge.h"
223#include "ios/web/public/web_thread.h"
224#import "ios/web/web_state/ui/crw_web_controller.h"
225#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17226#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13227#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
228#include "net/ssl/ssl_info.h"
229#include "net/url_request/url_request_context_getter.h"
230#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
231#include "ui/base/l10n/l10n_util.h"
232#include "ui/base/l10n/l10n_util_mac.h"
233#include "ui/base/page_transition_types.h"
234#include "url/gurl.h"
235
stkhapuginf58b10d02017-04-10 13:36:17236#if !defined(__has_feature) || !__has_feature(objc_arc)
237#error "This file requires ARC support."
238#endif
239
sdefresnee65fd872016-12-19 13:38:13240using base::UserMetricsAction;
241using bookmarks::BookmarkNode;
242
243class BrowserBookmarkModelBridge;
244class InfoBarContainerDelegateIOS;
245
sdefresnee65fd872016-12-19 13:38:13246namespace {
247
248typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
249 // Note: these values must match the ContextMenuOption enum in histograms.xml.
250 ACTION_OPEN_IN_NEW_TAB = 0,
251 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
252 ACTION_COPY_LINK_ADDRESS = 2,
253 ACTION_SAVE_IMAGE = 6,
254 ACTION_OPEN_IMAGE = 7,
255 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
256 ACTION_SEARCH_BY_IMAGE = 11,
257 ACTION_OPEN_JAVASCRIPT = 21,
258 ACTION_READ_LATER = 22,
259 NUM_ACTIONS = 23,
260};
261
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28262void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13263 if (is_image) {
264 if (is_link) {
265 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
266 NUM_ACTIONS);
267 } else {
268 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
269 NUM_ACTIONS);
270 }
271 } else {
272 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
273 NUM_ACTIONS);
274 }
275}
276
edchinf5150c682017-09-18 02:50:03277// Returns the status bar background color.
278UIColor* StatusBarBackgroundColor() {
279 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
280}
281
282// Duration of the toolbar animation.
283const NSTimeInterval kFullScreenControllerToolbarAnimationDuration = 0.3;
284
sdefresnee65fd872016-12-19 13:38:13285const CGFloat kVoiceSearchBarHeight = 59.0;
286
287// Dimensions to use when downsizing an image for search-by-image.
288const CGFloat kSearchByImageMaxImageArea = 90000.0;
289const CGFloat kSearchByImageMaxImageWidth = 600.0;
290const CGFloat kSearchByImageMaxImageHeight = 400.0;
291
sdefresnee65fd872016-12-19 13:38:13292enum HeaderBehaviour {
293 // The header moves completely out of the screen.
294 Hideable = 0,
295 // This header stays on screen and doesn't overlap with the content.
296 Visible,
297 // This header stay on screen and covers part of the content.
298 Overlap
299};
300
sdefresnee65fd872016-12-19 13:38:13301const CGFloat kIPadFindBarOverlap = 11;
302
303bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24304 // Most URLs are allowed in incognito; the following is an exception.
305 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13306}
307
edchineeb4d422017-10-02 17:39:36308// Snackbar category for browser view controller.
309NSString* const kBrowserViewControllerSnackbarCategory =
310 @"BrowserViewControllerSnackbarCategory";
311
rohitrao005a6432017-03-16 20:52:42312} // namespace
sdefresnee65fd872016-12-19 13:38:13313
stkhapugin952ecef2017-04-11 12:11:45314#pragma mark - HeaderDefinition helper
315
316@interface HeaderDefinition : NSObject
317
318// The header view.
319@property(nonatomic, strong) UIView* view;
320// How to place the view, and its behaviour when the headers move.
321@property(nonatomic, assign) HeaderBehaviour behaviour;
322// Reduces the height of a header to adjust for shadows.
323@property(nonatomic, assign) CGFloat heightAdjustement;
324// Nudges that particular header up by this number of points.
325@property(nonatomic, assign) CGFloat inset;
326
327- (instancetype)initWithView:(UIView*)view
328 headerBehaviour:(HeaderBehaviour)behaviour
329 heightAdjustment:(CGFloat)heightAdjustment
330 inset:(CGFloat)inset;
331
332+ (instancetype)definitionWithView:(UIView*)view
333 headerBehaviour:(HeaderBehaviour)behaviour
334 heightAdjustment:(CGFloat)heightAdjustment
335 inset:(CGFloat)inset;
336
337@end
338
339@implementation HeaderDefinition
340@synthesize view = _view;
341@synthesize behaviour = _behaviour;
342@synthesize heightAdjustement = _heightAdjustement;
343@synthesize inset = _inset;
344
345+ (instancetype)definitionWithView:(UIView*)view
346 headerBehaviour:(HeaderBehaviour)behaviour
347 heightAdjustment:(CGFloat)heightAdjustment
348 inset:(CGFloat)inset {
349 return [[self alloc] initWithView:view
350 headerBehaviour:behaviour
351 heightAdjustment:heightAdjustment
352 inset:inset];
353}
354
355- (instancetype)initWithView:(UIView*)view
356 headerBehaviour:(HeaderBehaviour)behaviour
357 heightAdjustment:(CGFloat)heightAdjustment
358 inset:(CGFloat)inset {
359 self = [super init];
360 if (self) {
361 _view = view;
362 _behaviour = behaviour;
363 _heightAdjustement = heightAdjustment;
364 _inset = inset;
365 }
366 return self;
367}
368
369@end
370
371#pragma mark - BVC
372
Rohit Rao01e0e002017-08-14 20:49:43373@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43374 AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13375 CRWNativeContentProvider,
376 CRWWebStateDelegate,
377 DialogPresenterDelegate,
378 FullScreenControllerDelegate,
Mike Doughertya1ec26402017-08-23 19:46:31379 IOSCaptivePortalBlockingPageDelegate,
sdefresnee65fd872016-12-19 13:38:13380 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57381 NetExportTabHelperDelegate,
edchincd32fdf2017-10-25 12:45:45382 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13383 MFMailComposeViewControllerDelegate,
384 NewTabPageControllerObserver,
385 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27386 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13387 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55388 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13389 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11390 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59391 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31392 SideSwipeControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13393 SKStoreProductViewControllerDelegate,
394 SnapshotOverlayProvider,
395 StoreKitLauncher,
396 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19397 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38398 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13399 TabModelObserver,
400 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03401 TabStripPresentation,
sdefresnee65fd872016-12-19 13:38:13402 UIGestureRecognizerDelegate,
403 UpgradeCenterClientProtocol,
404 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04405 VoiceSearchBarOwner,
406 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13407 // The dependency factory passed on initialization. Used to vend objects used
408 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27409 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13410
411 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27412 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13413
sczsf1620e52017-10-02 22:54:46414 // Facade objects used by |_toolbarCoordinator|.
415 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13416 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
417 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
418
sdefresnee65fd872016-12-19 13:38:13419 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27420 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13421
Mike Doughertya1ec26402017-08-23 19:46:31422 // Handles displaying the captive portal login page.
423 CaptivePortalLoginCoordinator* _captivePortalLoginCoordinator;
424
sdefresnee65fd872016-12-19 13:38:13425 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27426 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13427
428 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27429 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13430
431 // Handles presentation of JavaScript dialogs.
432 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
433
justincohen75011c32017-04-28 16:31:39434 // Handles command dispatching.
435 CommandDispatcher* _dispatcher;
436
sdefresnee65fd872016-12-19 13:38:13437 // Keyboard commands provider. It offloads most of the keyboard commands
438 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27439 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13440
441 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
442 // to |-reparentToolbarController| will reset it to NO.
443 BOOL _isToolbarControllerRelinquished;
444
445 // The controller that owns the currently relinquished toolbar controller.
446 // The reference is weak because it's possible for the toolbar owner to be
447 // deallocated mid-animation due to memory pressure or a tab being closed
448 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27449 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13450
sdefresnee65fd872016-12-19 13:38:13451 // Used to inject Javascript implementing the PaymentRequest API and to
452 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27453 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13454
sdefresnee65fd872016-12-19 13:38:13455 // Used to display the Voice Search UI. Nil if not visible.
456 scoped_refptr<VoiceSearchController> _voiceSearchController;
457
gambard6299cc1d2017-02-21 13:06:03458 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27459 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03460
sdefresnee65fd872016-12-19 13:38:13461 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27462 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13463
sdefresnee65fd872016-12-19 13:38:13464 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27465 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13466
467 // Records the set of domains for which full screen alert has already been
468 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27469 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13470
471 // Adapter to let BVC be the delegate for WebState.
472 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
473
474 // YES if new tab is animating in.
475 BOOL _inNewTabAnimation;
476
477 // YES if Voice Search should be started when the new tab animation is
478 // finished.
479 BOOL _startVoiceSearchAfterNewTabAnimation;
480
481 // YES if the user interacts with the location bar.
482 BOOL _locationBarHasFocus;
483 // YES if a load was cancelled due to typing in the location bar.
484 BOOL _locationBarEditCancelledLoad;
485 // YES if waiting for a foreground tab due to expectNewForegroundTab.
486 BOOL _expectingForegroundTab;
487
Sylvain Defresne41170aa2017-06-15 10:25:20488 // Whether or not -shutdown has been called.
489 BOOL _isShutdown;
490
sdefresnee65fd872016-12-19 13:38:13491 // The ChromeBrowserState associated with this BVC.
492 ios::ChromeBrowserState* _browserState; // weak
493
494 // Whether or not Incognito* is enabled.
495 BOOL _isOffTheRecord;
496
497 // The last point within |_contentArea| that's received a touch.
498 CGPoint _lastTapPoint;
499
500 // The time at which |_lastTapPoint| was most recently set.
501 CFTimeInterval _lastTapTime;
502
503 // A single infobar container handles all infobars in all tabs. It keeps
504 // track of infobars for current tab (accessed via infobar helper of
505 // the current tab).
506 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
507
508 // Bridge class to deliver container change notifications to BVC.
509 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
510
511 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37512 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27513 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13514
515 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11516 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13517
sdefresnee65fd872016-12-19 13:38:13518 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27519 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13520
521 // Bridge to register for bookmark changes.
522 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
523
524 // Cached pointer to the bookmarks model.
525 bookmarks::BookmarkModel* _bookmarkModel; // weak
526
527 // The controller that shows the bookmarking UI after the user taps the star
528 // button.
stkhapuginc9eee7b2017-04-10 15:49:27529 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13530
sdefresnee65fd872016-12-19 13:38:13531 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27532 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13533
Eugene But56efc322017-08-11 14:03:44534 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28535 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13536
537 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27538 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13539
Jean-François Geyelin3d47c212017-08-03 09:24:09540 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27541 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13542
Rohit Rao01e0e002017-08-14 20:49:43543 // Coordinator for the share menu (Activity Services).
544 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
545
sdefresnee65fd872016-12-19 13:38:13546 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27547 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38548
Rohit Raocda0a992017-08-16 15:37:11549 // Coordinator for the QR scanner.
550 QRScannerLegacyCoordinator* _qrScannerCoordinator;
551
sczsdd860eba2017-08-10 01:55:38552 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13553 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53554
555 // Coordinator for displaying Sad Tab.
556 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27557
558 // Coordinator for Page Info UI.
559 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59560
561 // Coordinator for displaying Repost Form dialog.
562 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22563
edchin7f210cd2017-09-28 08:03:53564 // Coordinator for displaying snackbars.
565 SnackbarCoordinator* _snackbarCoordinator;
566
sczsf1620e52017-10-02 22:54:46567 // Coordinator for the toolbar.
568 LegacyToolbarCoordinator* _toolbarCoordinator;
569
Louis Romerod11747a2017-10-20 20:10:35570 // Coordinator for the External Search UI.
571 ExternalSearchCoordinator* _externalSearchCoordinator;
572
Justin Cohenb3170c32017-09-19 01:55:22573 // Fake status bar view used to blend the toolbar into the status bar.
574 UIView* _fakeStatusBarView;
sdefresnee65fd872016-12-19 13:38:13575}
576
577// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17578@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13579// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17580@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13581// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17582@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13583// Whether the current tab can enable the request desktop menu item.
584@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
585// Whether the sharing menu should be enabled.
586@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
587// Helper method to check web controller canShowFindBar method.
588@property(nonatomic, assign, readonly) BOOL canShowFindBar;
589// Whether the controller's view is currently available.
590// YES from viewWillAppear to viewWillDisappear.
591@property(nonatomic, assign, getter=isVisible) BOOL visible;
592// Whether the controller's view is currently visible.
593// YES from viewDidAppear to viewWillDisappear.
594@property(nonatomic, assign) BOOL viewVisible;
595// Whether the controller is currently dismissing a presented view controller.
596@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
597// Returns YES if the toolbar has not been scrolled out by fullscreen.
598@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
599 BOOL toolbarOnScreen;
600// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12601@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13602// Whether BVC prefers to hide the status bar. This value is used to determine
603// the response from the |prefersStatusBarHidden| method.
604@property(nonatomic, assign) BOOL hideStatusBar;
605// Whether the VoiceSearchBar should be displayed.
606@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
607// Coordinator for displaying a modal overlay with activity indicator to prevent
608// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17609@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13610 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39611// A block to be run when the |tabWasAdded:| method completes the animation
612// for the presentation of a new tab. Can be used to record performance metrics.
613@property(nonatomic, strong, nullable)
614 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42615// Coordinator for Recent Tabs.
616@property(nonatomic, strong)
617 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03618// Coordinator for tablet tab strip.
619@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
620// A weak reference to the view of the tab strip on tablet.
621@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13622
liaoyukeea9f3ee62017-03-07 22:05:39623// The user agent type used to load the currently visible page. User agent type
624// is NONE if there is no visible page or visible page is a native page.
625@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
626
stkhapugin952ecef2017-04-11 12:11:45627// Returns the header views, all the chrome on top of the page, including the
628// ones that cannot be scrolled off screen by full screen.
629@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
630
Cooper Knaakd0a974cd2017-08-10 18:05:47631// Used to display the new tab tip in-product help promotion bubble. |nil| if
632// the new tab tip bubble has not yet been presented. Once the bubble is
633// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38634@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47635 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38636
Helen Yang9175bd52017-08-12 00:28:40637// Used to display the new incognito tab tip in-product help promotion bubble.
638@property(nonatomic, strong)
639 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
640
sdefresnee65fd872016-12-19 13:38:13641// BVC initialization:
642// If the BVC is initialized with a valid browser state & tab model immediately,
643// the path is straightforward: functionality is enabled, and the UI is built
644// when -viewDidLoad is called.
645// If the BVC is initialized without a browser state or tab model, the tab model
646// and browser state may or may not be provided before -viewDidLoad is called.
647// In most cases, they will not, to improve startup performance.
648// In order to handle this, initialization of various aspects of BVC have been
649// broken out into the following functions, which have expectations (enforced
650// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
651
652// Registers for notifications.
653- (void)registerForNotifications;
654// Called when a tab is starting to load. If it's a link click or form
655// submission, the user is navigating away from any entries in the forward
656// history. Tell the toolbar so it can update the UI appropriately.
657// See the warning on [Tab webWillStartLoadingURL] about invocation of this
658// method sequence by malicious pages.
659- (void)pageLoadStarting:(NSNotification*)notify;
660// Called when a tab actually starts loading.
661- (void)pageLoadStarted:(NSNotification*)notify;
662// Called when a tab finishes loading. Update the Omnibox with the url and
663// stop any page load progess display.
664- (void)pageLoadComplete:(NSNotification*)notify;
665// Called when a tab is deselected in the model.
666// This notification also occurs when a tab is closed.
667- (void)tabDeselected:(NSNotification*)notify;
668// Animates sliding current tab and rotate-entering new tab while new tab loads
669// in background on the iPhone only.
670- (void)tabWasAdded:(NSNotification*)notify;
671
672// Updates non-view-related functionality with the given browser state and tab
673// model.
674// Does not matter whether or not the view has been loaded.
675- (void)updateWithTabModel:(TabModel*)model
676 browserState:(ios::ChromeBrowserState*)browserState;
677// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
678// the status bar to mimic this layout.
679- (void)installFakeStatusBar;
680// Builds the UI parts of tab strip and the toolbar. Does not matter whether
681// or not browser state and tab model are valid.
682- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50683// Sets up the constraints on the toolbar.
684- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13685// Updates view-related functionality with the given tab model and browser
686// state. The view must have been loaded. Uses |_browserState| and |_model|.
687- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22688// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13689- (void)setUpViewLayout;
sdefresnee65fd872016-12-19 13:38:13690// Makes |tab| the currently visible tab, displaying its view. Calls
691// -selectedTabChanged on the toolbar only if |newSelection| is YES.
692- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
693// Initializes the bookmark interaction controller if not already initialized.
694- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13695// Add all delegates to the provided |tab|.
696- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14697// Remove delegates from the provided |tab|.
698- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13699// Closes the current tab, with animation if applicable.
700- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13701// Show the bookmarks page.
702- (void)showAllBookmarks;
703// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23704- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13705// Dismisses the "rate this app" dialog.
706- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32707// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13708- (BOOL)isTabNativePage:(Tab*)tab;
709// Returns the view to use when animating a page in or out, positioning it to
710// fill the content area but not actually adding it to the view hierarchy.
711- (UIImageView*)pageOpenCloseAnimationView;
712// Returns the view to use when animating full screen NTP paper in, filling the
713// entire screen but not actually adding it to the view hierarchy.
714- (UIImageView*)pageFullScreenOpenCloseAnimationView;
715// Updates the toolbar display based on the current tab.
716- (void)updateToolbar;
717// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12718// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13719- (void)updateDialogPresenterActiveState;
720// Dismisses popups and modal dialogs that are displayed above the BVC upon size
721// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
722// is performed.
723// TODO(crbug.com/522721): Support size changes for all popups and modal
724// dialogs.
725- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47726
727// Returns a bubble associated with an in-product help promotion if
728// it is valid to show the promotion and |nil| otherwise. |feature| is the
729// base::Feature object associated with the given promotion. |direction| is the
730// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21731// arrow on the button. |text| is the text displayed by the bubble. This method
732// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47733- (BubbleViewControllerPresenter*)
734bubblePresenterForFeature:(const base::Feature&)feature
735 direction:(BubbleArrowDirection)direction
736 alignment:(BubbleAlignment)alignment
737 text:(NSString*)text;
738
Cooper Knaak120cee5e2017-08-10 20:57:00739// Waits to present a bubble associated with the new tab tip in-product help
740// promotion until the feature engagement tracker database is fully initialized.
741// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
742// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11743// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21744// determines it is not valid to present it. This method requires that
745// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00746- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11747// Optionally presents a bubble associated with the new tab tip in-product help
748// promotion. If the feature engagement tracker determines it is valid to show
749// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
750// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21751// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
752// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00753- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40754// Waits to present a bubble associated with the new incognito tab tip
755// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21756// fully initialized. This method requires that |self.browserState| is
757// not NULL.
Helen Yang9175bd52017-08-12 00:28:40758- (void)presentNewIncognitoTabTipBubbleOnInitialized;
759// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21760// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40761- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21762// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
763// eligible. Only one can be eligible per session (as enforced by the
764// FeatureEngagementTracker). If neither is eligible, neither bubble is
765// presented. This method requires that |self.browserState| is not NULL.
766- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00767
sdefresnee65fd872016-12-19 13:38:13768// Update find bar with model data. If |shouldFocus| is set to YES, the text
769// field will become first responder.
770- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13771// Hide find bar.
772- (void)hideFindBarWithAnimation:(BOOL)animate;
773// Shows find bar. If |selectText| is YES, all text inside the Find Bar
774// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
775// set to be first responder.
776- (void)showFindBarWithAnimation:(BOOL)animate
777 selectText:(BOOL)selectText
778 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36779
sdefresnee65fd872016-12-19 13:38:13780// The infobar state (typically height) has changed.
781- (void)infoBarContainerStateChanged:(bool)is_animating;
782// Adds a CardView on top of the contentArea either taking the size of the full
783// screen or just the size of the space under the header.
784// Returns the CardView that was added.
785- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
786// Called when either a tab finishes loading or when a tab with finished content
787// is added directly to the model via pre-rendering. The tab must be non-nil and
788// must be a member of the tab model controlled by this BrowserViewController.
789- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
790// Evaluates Javascript asynchronously using the current page context.
791- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36792// Shows a self-dismissing snackbar displaying |message|.
793- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13794// Induces an intentional crash in the browser process.
795- (void)induceBrowserCrash;
796// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17797- (void)managePermissionAndSaveImage:(NSData*)data
798 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13799// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24800// saved as a temporary file on disk then saved in photos. Saving will happen
801// on a background sequence and the completion block will be invoked on that
802// sequence.
803- (void)saveImage:(NSData*)data
804 withFileExtension:(NSString*)fileExtension
805 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13806// Called when Chrome has been denied access to the photos or videos and the
807// user can change it.
808// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
809// settings. Dismiss previous alert if it has not been dismissed yet.
810- (void)displayImageErrorAlertWithSettingsOnMainQueue;
811// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
812// previous alert if it has not been dismissed yet.
813- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
814// Called when Chrome has been denied access to the photos or videos and the
815// user cannot change it.
816// Shows a privacy alert on the main queue, with errorContent as the message.
817// Dismisses previous alert if it has not been dismissed yet.
818- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
819// Called with the results of saving a picture in the photo album. If error is
820// nil the save succeeded.
821- (void)finishSavingImageWithError:(NSError*)error;
822// Provides a view that encompasses currently displayed infobar(s) or nil
823// if no infobar is presented.
824- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
825// Returns a vertical infobar offset relative to the tab content.
826- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
827// Provides a view that encompasses the voice search bar if it's displayed or
828// nil if the voice search bar isn't displayed.
829- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
830// Returns a vertical voice search bar offset relative to the tab content.
831- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
832// Lazily instantiates |_voiceSearchController|.
833- (void)ensureVoiceSearchControllerCreated;
834// Lazily instantiates |_voiceSearchBar| and adds it to the view.
835- (void)ensureVoiceSearchBarCreated;
836// Shows/hides the voice search bar.
837- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
838// The LogoAnimationControllerOwner to be used for the next logo transition
839// animation.
840- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13841// Returns the footer view if one exists (e.g. the voice search bar).
842- (UIView*)footerView;
843// Returns the height of the header view for the tab model's current tab.
844- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13845// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45846- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13847 atOffset:(CGFloat)headerOffset;
848// Returns the y coordinate for the footer's frame when animating the footer
849// in/out of fullscreen.
850- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
851// Called when the animation for setting the header view's offset is finished.
852// |completed| should indicate if the animation finished completely or was
853// interrupted. |offset| should indicate the header offset after the animation.
854// |dragged| should indicate if the header moved due to the user dragging.
855- (void)fullScreenController:(FullScreenController*)controller
856 headerAnimationCompleted:(BOOL)completed
857 offset:(CGFloat)offset;
858// Performs a search with the image at the given url. The referrer is used to
859// download the image.
860- (void)searchByImageAtURL:(const GURL&)url
861 referrer:(const web::Referrer)referrer;
862// Saves the image at the given URL on the system's album. The referrer is used
863// to download the image.
864- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
865
Mark Cogandfcdea72017-07-18 13:47:38866// Record the last tap point based on the |originPoint| (if any) passed in
867// |command|.
868- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13869// Get return the last stored |_lastTapPoint| if it's been set within the past
870// second.
871- (CGPoint)lastTapPoint;
872// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
873- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
874// Returns the native controller being used by |tab|'s web controller.
875- (id)nativeControllerForTab:(Tab*)tab;
876// Installs the BVC as overscroll actions controller of |nativeContent| if
877// needed. Sets the style of the overscroll actions toolbar.
878- (void)setOverScrollActionControllerToStaticNativeContent:
879 (StaticHtmlNativeContent*)nativeContent;
880// Whether the BVC should declare keyboard commands.
881- (BOOL)shouldRegisterKeyboardCommands;
882// Adds the given url to the reading list.
883- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
884@end
885
886class InfoBarContainerDelegateIOS
887 : public infobars::InfoBarContainer::Delegate {
888 public:
889 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
890 : controller_(controller) {}
891
892 ~InfoBarContainerDelegateIOS() override {}
893
894 private:
895 SkColor GetInfoBarSeparatorColor() const override {
896 NOTIMPLEMENTED();
897 return SK_ColorBLACK;
898 }
899
900 int ArrowTargetHeightForInfoBar(
901 size_t index,
902 const gfx::SlideAnimation& animation) const override {
903 return 0;
904 }
905
906 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
907 int arrow_target_height,
908 int bar_target_height,
909 int* arrow_height,
910 int* arrow_half_width,
911 int* bar_height) const override {
912 DCHECK_NE(-1, bar_target_height)
913 << "Infobars don't have a default height on iOS";
914 *arrow_height = 0;
915 *arrow_half_width = 0;
916 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
917 }
918
919 void InfoBarContainerStateChanged(bool is_animating) override {
920 [controller_ infoBarContainerStateChanged:is_animating];
921 }
922
923 bool DrawInfoBarArrows(int* x) const override { return false; }
924
stkhapuginf58b10d02017-04-10 13:36:17925 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13926};
927
928// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
929@interface BrowserViewController (BookmarkBridgeMethods)
930// If a bookmark matching the currentTab url is added or moved, update the
931// toolbar state so the star highlight is in sync.
932- (void)bookmarkNodeModified:(const BookmarkNode*)node;
933- (void)allBookmarksRemoved;
934@end
935
936// Handle notification that bookmarks has been removed changed so we can update
937// the bookmarked star icon.
938class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
939 public:
940 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
941 : owner_(owner) {}
942
943 ~BrowserBookmarkModelBridge() override {}
944
945 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
946 const BookmarkNode* parent,
947 int old_index,
948 const BookmarkNode* node,
949 const std::set<GURL>& removed_urls) override {
950 [owner_ bookmarkNodeModified:node];
951 }
952
953 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
954 bool ids_reassigned) override {}
955
956 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
957 const BookmarkNode* old_parent,
958 int old_index,
959 const BookmarkNode* new_parent,
960 int new_index) override {}
961
962 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
963 const BookmarkNode* parent,
964 int index) override {
965 [owner_ bookmarkNodeModified:parent->GetChild(index)];
966 }
967
968 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
969 const BookmarkNode* node) override {}
970
971 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
972 const BookmarkNode* node) override {}
973
974 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
975 const BookmarkNode* node) override {}
976
977 void BookmarkAllUserNodesRemoved(
978 bookmarks::BookmarkModel* model,
979 const std::set<GURL>& removed_urls) override {
980 [owner_ allBookmarksRemoved];
981 }
982
983 private:
stkhapuginf58b10d02017-04-10 13:36:17984 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13985};
986
987@implementation BrowserViewController
988
989@synthesize contentArea = _contentArea;
990@synthesize typingShield = _typingShield;
991@synthesize active = _active;
992@synthesize visible = _visible;
993@synthesize viewVisible = _viewVisible;
994@synthesize dismissingModal = _dismissingModal;
995@synthesize hideStatusBar = _hideStatusBar;
996@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
997@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39998@synthesize foregroundTabWasAddedCompletionBlock =
999 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:401000@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
1001@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:421002@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:031003@synthesize tabStripCoordinator = _tabStripCoordinator;
1004@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:131005
1006#pragma mark - Object lifecycle
1007
Mark Cogan5e3da152017-07-11 15:57:301008- (instancetype)
1009 initWithTabModel:(TabModel*)model
1010 browserState:(ios::ChromeBrowserState*)browserState
1011 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
1012applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:131013 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
1014 if (self) {
1015 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:171016
stkhapuginc9eee7b2017-04-10 15:49:271017 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:271018 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
1019 presentingViewController:self];
justincohen75011c32017-04-28 16:31:391020 _dispatcher = [[CommandDispatcher alloc] init];
1021 [_dispatcher startDispatchingToTarget:self
1022 forProtocol:@protocol(UrlLoader)];
1023 [_dispatcher startDispatchingToTarget:self
1024 forProtocol:@protocol(WebToolbarDelegate)];
1025 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241026 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301027 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1028 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321029 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1030 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1031 // dispatched to the endpoint as well. Since this is potentially
1032 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1033 DCHECK(!applicationCommandEndpoint ||
1034 [applicationCommandEndpoint
1035 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1036 [_dispatcher
1037 startDispatchingToTarget:applicationCommandEndpoint
1038 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391039
edchin7f210cd2017-09-28 08:03:531040 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
1041 _snackbarCoordinator.dispatcher = _dispatcher;
1042 [_snackbarCoordinator start];
1043
sdefresnee65fd872016-12-19 13:38:131044 _javaScriptDialogPresenter.reset(
1045 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1046 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1047 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151048 [[UpgradeCenter sharedInstance] registerClient:self
1049 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131050 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131051 if (model && browserState)
1052 [self updateWithTabModel:model browserState:browserState];
1053 if ([[NSUserDefaults standardUserDefaults]
1054 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271055 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131056 }
1057 }
1058 return self;
1059}
1060
1061- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1062 bundle:(NSBundle*)nibBundleOrNil {
1063 NOTREACHED();
1064 return nil;
1065}
1066
1067- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1068 NOTREACHED();
1069 return nil;
1070}
1071
1072- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201073 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131074}
1075
1076#pragma mark - Accessibility
1077
1078- (BOOL)accessibilityPerformEscape {
1079 [self dismissPopups];
1080 return YES;
1081}
1082
1083#pragma mark - Properties
1084
edchin3365c7d2017-09-01 22:20:371085- (id<ApplicationCommands,
1086 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371087 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531088 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371089 UrlLoader,
1090 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561091 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531092 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1093 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241094}
1095
Gauthier Ambard29939db12017-10-30 16:47:311096- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
1097 return _toolbarCoordinator;
1098}
1099
sdefresnee65fd872016-12-19 13:38:131100- (void)setActive:(BOOL)active {
1101 if (_active == active) {
1102 return;
1103 }
1104 _active = active;
1105
1106 // If not active, display an activity indicator overlay over the view to
1107 // prevent interaction with the web page.
1108 // TODO(crbug.com/637093): This coordinator should be managed by the
1109 // coordinator used to present BrowserViewController, when implemented.
1110 if (active) {
1111 [self.activityOverlayCoordinator stop];
1112 self.activityOverlayCoordinator = nil;
1113 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171114 self.activityOverlayCoordinator =
1115 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131116 [self.activityOverlayCoordinator start];
1117 }
1118
1119 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091120 ActiveStateManager* active_state_manager =
1121 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131122 active_state_manager->SetActive(active);
1123 }
1124
1125 [_model setWebUsageEnabled:active];
1126 [self updateDialogPresenterActiveState];
1127
1128 if (active) {
1129 // Make sure the tab (if any; it's possible to get here without a current
1130 // tab if the caller is about to create one) ends up on screen completely.
1131 Tab* currentTab = [_model currentTab];
1132 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441133 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131134 if (_expectingForegroundTab)
1135 [currentTab.webController setOverlayPreviewMode:YES];
1136 if (currentTab)
1137 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341138 } else {
1139 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131140 }
sdefresnee65fd872016-12-19 13:38:131141 [_paymentRequestManager enablePaymentRequest:active];
1142
1143 [self setNeedsStatusBarAppearanceUpdate];
1144}
1145
1146- (void)setPrimary:(BOOL)primary {
1147 [_model setPrimary:primary];
1148 if (primary) {
1149 [self updateDialogPresenterActiveState];
1150 } else {
1151 self.dialogPresenter.active = false;
1152 }
1153}
1154
1155- (BOOL)isPlayingTTS {
1156 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1157}
1158
sdefresne6165c8742017-01-16 15:42:021159- (ios::ChromeBrowserState*)browserState {
1160 return _browserState;
1161}
1162
1163- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271164 return _model;
sdefresne6165c8742017-01-16 15:42:021165}
1166
sdefresnee65fd872016-12-19 13:38:131167- (SideSwipeController*)sideSwipeController {
1168 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271169 _sideSwipeController =
1170 [[SideSwipeController alloc] initWithTabModel:_model
1171 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131172 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311173 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131174 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031175 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131176 }
1177 return _sideSwipeController;
1178}
1179
sdefresnee65fd872016-12-19 13:38:131180- (DialogPresenter*)dialogPresenter {
1181 return _dialogPresenter;
1182}
1183
sdefresnee65fd872016-12-19 13:38:131184- (BOOL)canUseDesktopUserAgent {
1185 Tab* tab = [_model currentTab];
1186 if ([self isTabNativePage:tab])
1187 return NO;
1188
1189 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441190 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131191}
1192
1193// Whether the sharing menu should be shown.
1194- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191195 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251196 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131197}
1198
1199- (BOOL)canShowFindBar {
1200 // Make sure web controller can handle find in page.
1201 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421202 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131203 return NO;
rohitrao005a6432017-03-16 20:52:421204 }
sdefresnee65fd872016-12-19 13:38:131205
rohitrao005a6432017-03-16 20:52:421206 auto* helper = FindTabHelper::FromWebState(tab.webState);
1207 return (helper && helper->CurrentPageSupportsFindInPage() &&
1208 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131209}
1210
liaoyukeea9f3ee62017-03-07 22:05:391211- (web::UserAgentType)userAgentType {
1212 web::WebState* webState = [_model currentTab].webState;
1213 if (!webState)
1214 return web::UserAgentType::NONE;
1215 web::NavigationItem* visibleItem =
1216 webState->GetNavigationManager()->GetVisibleItem();
1217 if (!visibleItem)
1218 return web::UserAgentType::NONE;
1219
1220 return visibleItem->GetUserAgentType();
1221}
1222
sdefresnee65fd872016-12-19 13:38:131223- (void)setVisible:(BOOL)visible {
1224 if (_visible == visible)
1225 return;
1226 _visible = visible;
1227}
1228
1229- (void)setViewVisible:(BOOL)viewVisible {
1230 if (_viewVisible == viewVisible)
1231 return;
1232 _viewVisible = viewVisible;
1233 self.visible = viewVisible;
1234 [self updateDialogPresenterActiveState];
1235}
1236
1237- (BOOL)isToolbarOnScreen {
1238 return [self headerHeight] - [self currentHeaderOffset] > 0;
1239}
1240
kkhorimotoa44349c12017-04-12 23:02:121241- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1242 if (_inNewTabAnimation == inNewTabAnimation)
1243 return;
1244 _inNewTabAnimation = inNewTabAnimation;
1245 [self updateDialogPresenterActiveState];
1246}
1247
sdefresnee65fd872016-12-19 13:38:131248- (BOOL)isInNewTabAnimation {
1249 return _inNewTabAnimation;
1250}
1251
1252- (BOOL)shouldShowVoiceSearchBar {
1253 // On iPads, the voice search bar should only be shown for regular horizontal
1254 // size class configurations. It should always be shown for voice search
1255 // results Tabs on iPhones, including configurations with regular horizontal
1256 // size classes (i.e. landscape iPhone 6 Plus).
1257 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1258 UIUserInterfaceSizeClassCompact;
1259 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1260 (!IsIPadIdiom() || compactWidth);
1261}
1262
1263- (void)setHideStatusBar:(BOOL)hideStatusBar {
1264 if (_hideStatusBar == hideStatusBar)
1265 return;
1266 _hideStatusBar = hideStatusBar;
1267 [self setNeedsStatusBarAppearanceUpdate];
1268}
1269
1270#pragma mark - IBActions
1271
1272- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461273 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131274}
1275
Cooper Knaakd0a974cd2017-08-10 18:05:471276- (void)userEnteredTabSwitcher {
1277 if ([self.tabTipBubblePresenter isUserEngaged]) {
1278 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1279 }
1280}
1281
Cooper Knaake963d6702017-08-11 21:03:111282- (void)presentBubblesIfEligible {
1283 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401284 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111285}
1286
sdefresnee65fd872016-12-19 13:38:131287#pragma mark - UIViewController methods
1288
1289// Perform additional set up after loading the view, typically from a nib.
1290- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091291 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281292 initialViewsRect.origin.y += StatusBarHeight();
1293 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131294 UIViewAutoresizing initialViewAutoresizing =
1295 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1296
stkhapuginf58b10d02017-04-10 13:36:171297 self.contentArea =
1298 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131299 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171300 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131301 self.typingShield.autoresizingMask = initialViewAutoresizing;
1302 [self.typingShield addTarget:self
1303 action:@selector(shieldWasTapped:)
1304 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131305 self.view.autoresizingMask = initialViewAutoresizing;
1306 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1307 [self.view addSubview:self.contentArea];
1308 [self.view addSubview:self.typingShield];
1309 [super viewDidLoad];
1310
1311 // Install fake status bar for iPad iOS7
1312 [self installFakeStatusBar];
1313 [self buildToolbarAndTabStrip];
1314 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501315 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
1316 [self addConstraintsToToolbar];
1317 }
sdefresnee65fd872016-12-19 13:38:131318 // If the tab model and browser state are valid, finish initialization.
1319 if (_model && _browserState)
1320 [self addUIFunctionalityForModelAndBrowserState];
1321
1322 // Add a tap gesture recognizer to save the last tap location for the source
1323 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271324 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1325 initWithTarget:self
1326 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131327 [tapRecognizer setDelegate:self];
1328 [tapRecognizer setCancelsTouchesInView:NO];
1329 [_contentArea addGestureRecognizer:tapRecognizer];
1330}
1331
Justin Cohenb3170c32017-09-19 01:55:221332- (void)viewSafeAreaInsetsDidChange {
1333 [super viewSafeAreaInsetsDidChange];
1334 // Gate this behind iPhone X, since it's currently the only device that
1335 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501336 if (IsIPhoneX()) {
Justin Cohenb3170c32017-09-19 01:55:221337 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501338 }
1339 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
Gauthier Ambard100670f72017-10-27 09:54:271340 // TODO(crbug.com/778236): Check if this call can be removed once the
1341 // Toolbar is a contained ViewController.
1342 [_toolbarCoordinator.toolbarController viewSafeAreaInsetsDidChange];
1343 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501344 }
Justin Cohenb3170c32017-09-19 01:55:221345}
1346
sdefresnee65fd872016-12-19 13:38:131347- (void)viewDidAppear:(BOOL)animated {
1348 [super viewDidAppear:animated];
1349 self.viewVisible = YES;
1350 [self updateDialogPresenterActiveState];
Gregory Chatzinoff541b8642017-10-25 00:25:211351
1352 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1353 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1354 // check for |self.browserState| before calling the presenting the bubbles.
1355 if (self.browserState) {
1356 [self presentBubblesIfEligible];
1357 }
sdefresnee65fd872016-12-19 13:38:131358}
1359
1360- (void)viewWillAppear:(BOOL)animated {
1361 [super viewWillAppear:animated];
1362
Rohit Rao9a8ad772017-10-30 22:35:591363 // Reparent the toolbar if it's been relinquished. If the tab switcher
1364 // presentation experiment is enabled, only do this if the parent VC is not
1365 // currently being presented. Otherwise, reparenting here would remove the
1366 // toolbar from the tab switcher while the switcher is in the process of
1367 // animating.
1368 if (_isToolbarControllerRelinquished) {
1369 if (!TabSwitcherPresentsBVCEnabled() ||
1370 (!self.beingPresented && !self.parentViewController.beingPresented)) {
1371 [self reparentToolbarController];
1372 }
1373 }
sdefresnee65fd872016-12-19 13:38:131374
1375 self.visible = YES;
1376
1377 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251378 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131379 _infoBarContainer->RestoreInfobars();
1380 }
1381
1382 // If the controller is suspended, or has been paged out due to low memory,
1383 // updating the view will be handled when it's displayed again.
1384 if (![_model webUsageEnabled] || !self.contentArea)
1385 return;
1386 // Update the displayed tab (if any; the switcher may not have created one
1387 // yet) in case it changed while showing the switcher.
1388 Tab* currentTab = [_model currentTab];
1389 if (currentTab)
1390 [self displayTab:currentTab isNewSelection:YES];
1391}
1392
1393- (void)viewWillDisappear:(BOOL)animated {
1394 self.viewVisible = NO;
1395 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131396 [[_model currentTab] wasHidden];
1397 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251398 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131399 _infoBarContainer->SuspendInfobars();
1400 }
1401 [super viewWillDisappear:animated];
1402}
1403
1404- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1405 duration:(NSTimeInterval)duration {
1406 [super willRotateToInterfaceOrientation:orient duration:duration];
1407 [self dismissPopups];
1408 [self reshowFindBarIfNeededWithCoordinator:nil];
1409}
1410
1411- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1412 [super didRotateFromInterfaceOrientation:orient];
1413
1414 // This reinitializes the toolbar, including updating the Overlay View,
1415 // if there is one.
1416 [self updateToolbar];
1417 [self infoBarContainerStateChanged:false];
1418}
1419
1420- (BOOL)prefersStatusBarHidden {
1421 return self.hideStatusBar;
1422}
1423
1424// Called when in the foreground and the OS needs more memory. Release as much
1425// as possible.
1426- (void)didReceiveMemoryWarning {
1427 // Releases the view if it doesn't have a superview.
1428 [super didReceiveMemoryWarning];
1429
1430 // Release any cached data, images, etc that aren't in use.
1431 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1432 // but since the FaviconCache uses obj-c in the header, it can't be included
1433 // there.
1434 if (_browserState) {
1435 FaviconLoader* loader =
1436 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1437 _browserState);
1438 if (loader)
1439 loader->PurgeCache();
1440 }
1441
1442 if (![self isViewLoaded]) {
1443 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1444 // as the BrowserViewController.
1445 self.contentArea = nil;
1446 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271447 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131448 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271449 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421450 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461451 _toolbarCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271452 _toolbarModelDelegate = nil;
1453 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031454 [self.tabStripCoordinator stop];
1455 self.tabStripCoordinator = nil;
1456 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271457 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131458 }
1459}
1460
1461- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1462 [super traitCollectionDidChange:previousTraitCollection];
1463 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1464 // because in some cases the presented view controller isn't a child of the
1465 // BVC in the view controller hierarchy (some intervening object isn't a
1466 // view controller).
1467 [self.presentedViewController
1468 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131469 // Update voice search bar visibility.
1470 [self updateVoiceSearchBarVisibilityAnimated:NO];
1471}
1472
1473- (void)viewWillTransitionToSize:(CGSize)size
1474 withTransitionCoordinator:
1475 (id<UIViewControllerTransitionCoordinator>)coordinator {
1476 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1477 [self dismissPopups];
1478 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1479}
1480
1481- (void)reshowFindBarIfNeededWithCoordinator:
1482 (id<UIViewControllerTransitionCoordinator>)coordinator {
1483 if (![_findBarController isFindInPageShown])
1484 return;
1485
1486 // Record focused state.
1487 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1488
1489 [self hideFindBarWithAnimation:NO];
1490
stkhapuginc9eee7b2017-04-10 15:49:271491 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131492 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1493 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271494 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131495 if (strongSelf)
1496 [strongSelf showFindBarWithAnimation:NO
1497 selectText:NO
1498 shouldFocus:isFocusedBeforeReshow];
1499 };
1500
1501 BOOL enqueued =
1502 [coordinator animateAlongsideTransition:nil completion:completion];
1503 if (!enqueued) {
1504 completion(nil);
1505 }
1506}
1507
1508- (void)dismissViewControllerAnimated:(BOOL)flag
1509 completion:(void (^)())completion {
1510 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271511 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131512 [super dismissViewControllerAnimated:flag
1513 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271514 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131515 [strongSelf setDismissingModal:NO];
1516 [strongSelf setPresenting:NO];
1517 if (completion)
1518 completion();
1519 [[strongSelf dialogPresenter] tryToPresent];
1520 }];
1521}
1522
1523- (void)presentViewController:(UIViewController*)viewControllerToPresent
1524 animated:(BOOL)flag
1525 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271526 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131527 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1528 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1529 // presented, to show a temporary view of the launch screen and then remove it
1530 // when the controller for the FRE has been presented. This fix should be
1531 // removed when the FRE startup code is rewritten.
1532 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1533 experimental_flags::AlwaysDisplayFirstRun()) &&
1534 !tests_hook::DisableFirstRun();
1535 // These if statements check that |presentViewController| is being called for
1536 // the FRE case.
1537 if (firstRunLaunch &&
1538 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1539 UINavigationController* navController =
1540 base::mac::ObjCCastStrict<UINavigationController>(
1541 viewControllerToPresent);
1542 if ([navController.topViewController
1543 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1544 self.hideStatusBar = YES;
1545
1546 // Load view from Launch Screen and add it to window.
1547 NSBundle* mainBundle = base::mac::FrameworkBundle();
1548 NSArray* topObjects =
1549 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1550 UIViewController* launchScreenController =
1551 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1552 // |launchScreenView| is loaded as an autoreleased object, and is retained
1553 // by the |completion| block below.
1554 UIView* launchScreenView = launchScreenController.view;
1555 launchScreenView.userInteractionEnabled = NO;
1556 launchScreenView.frame = self.view.window.bounds;
1557 [self.view.window addSubview:launchScreenView];
1558
1559 // Replace the completion handler sent to the superclass with one which
1560 // removes |launchScreenView| and resets the status bar. If |completion|
1561 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271562 __weak BrowserViewController* weakSelf = self;
1563 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131564 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271565 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131566 if (completion)
1567 completion();
stkhapuginc9eee7b2017-04-10 15:49:271568 };
sdefresnee65fd872016-12-19 13:38:131569 }
1570 }
1571
1572 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171573 if ([_sideSwipeController inSwipe]) {
1574 [_sideSwipeController resetContentView];
1575 }
sdefresnee65fd872016-12-19 13:38:131576
1577 [super presentViewController:viewControllerToPresent
1578 animated:flag
1579 completion:finalCompletionHandler];
1580}
1581
1582#pragma mark - Notification handling
1583
1584- (void)registerForNotifications {
1585 DCHECK(_model);
1586 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1587 [defaultCenter addObserver:self
1588 selector:@selector(pageLoadStarting:)
1589 name:kTabModelTabWillStartLoadingNotification
1590 object:_model];
1591 [defaultCenter addObserver:self
1592 selector:@selector(pageLoadStarted:)
1593 name:kTabModelTabDidStartLoadingNotification
1594 object:_model];
1595 [defaultCenter addObserver:self
1596 selector:@selector(pageLoadComplete:)
1597 name:kTabModelTabDidFinishLoadingNotification
1598 object:_model];
1599 [defaultCenter addObserver:self
1600 selector:@selector(tabDeselected:)
1601 name:kTabModelTabDeselectedNotification
1602 object:_model];
1603 [defaultCenter addObserver:self
1604 selector:@selector(tabWasAdded:)
1605 name:kTabModelNewTabWillOpenNotification
1606 object:_model];
1607}
1608
1609- (void)pageLoadStarting:(NSNotification*)notify {
1610 Tab* tab = notify.userInfo[kTabModelTabKey];
1611 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511612
1613 // Stop any Find in Page searches and close the find bar when navigating to a
1614 // new page.
1615 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361616
sdefresnee65fd872016-12-19 13:38:131617 if (tab == [_model currentTab]) {
1618 // TODO(pinkerton): Fill in here about hiding the forward button on
1619 // navigation.
1620 }
1621}
1622
1623- (void)pageLoadStarted:(NSNotification*)notify {
1624 Tab* tab = notify.userInfo[kTabModelTabKey];
1625 DCHECK(tab);
1626 if (tab == [_model currentTab]) {
1627 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461628 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131629 }
1630 [self updateVoiceSearchBarVisibilityAnimated:NO];
1631 }
1632}
1633
1634- (void)pageLoadComplete:(NSNotification*)notify {
1635 // Update the UI, but only if the current tab.
1636 Tab* tab = notify.userInfo[kTabModelTabKey];
1637 if (tab == [_model currentTab]) {
1638 // There isn't any need to update the toolbar here. When the page finishes,
1639 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1640 }
1641
1642 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1643
1644 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1645}
1646
1647- (void)tabDeselected:(NSNotification*)notify {
1648 DCHECK(notify);
1649 Tab* tab = notify.userInfo[kTabModelTabKey];
1650 DCHECK(tab);
1651 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221652 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131653}
1654
1655- (void)tabWasAdded:(NSNotification*)notify {
1656 Tab* tab = notify.userInfo[kTabModelTabKey];
1657 DCHECK(tab);
1658
Eugene But56efc322017-08-11 14:03:441659 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131660
1661 // When adding new tabs, check what kind of reminder infobar should
1662 // be added to the new tab. Try to add only one of them.
1663 // This check is done when a new tab is added either through the Tools Menu
1664 // "New Tab" or through "New Tab" in Stack View Controller. This method
1665 // is called after a new tab has added and finished initial navigation.
1666 // If this is added earlier, the initial navigation may end up clearing
1667 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301668 web::WebState* webState = tab.webState;
1669 DCHECK(webState);
1670
1671 infobars::InfoBarManager* infoBarManager =
1672 InfoBarManagerImpl::FromWebState(webState);
1673 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131674 forTabId:[tab tabId]];
edchinbb8ba892017-09-12 15:44:031675 if (!ReSignInInfoBarDelegate::Create(_browserState, tab, self.dispatcher)) {
edchin9cad67b2017-09-11 20:13:571676 DisplaySyncErrors(_browserState, tab, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:131677 }
1678
1679 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501680 // phone-specific. Call the foreground tab added completion block; for
1681 // iPhones, this will get executed after the animation has finished.
1682 if (IsIPadIdiom()) {
1683 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431684 // This callback is called before webState is activated (on
1685 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1686 // callback asynchronously to be sure the activation is complete.
1687 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011688 // Test existence again as the block may have been deleted.
1689 if (self.foregroundTabWasAddedCompletionBlock) {
1690 self.foregroundTabWasAddedCompletionBlock();
1691 self.foregroundTabWasAddedCompletionBlock = nil;
1692 }
Olivier Robinc7e46242017-09-06 07:55:431693 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501694 }
sdefresnee65fd872016-12-19 13:38:131695 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501696 }
sdefresnee65fd872016-12-19 13:38:131697
1698 // Do nothing if browsing is currently suspended. The BVC will set everything
1699 // up correctly when browsing resumes.
1700 if (!self.visible || ![_model webUsageEnabled])
1701 return;
1702
1703 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1704
1705 // Block that starts voice search at the end of new Tab animation if
1706 // necessary.
1707 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1708 if (_startVoiceSearchAfterNewTabAnimation) {
1709 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001710 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131711 }
1712 };
1713
kkhorimotoa44349c12017-04-12 23:02:121714 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131715 if (!inBackground) {
1716 UIView* animationParentView = _contentArea;
1717 // Create the new page image, and load with the new tab page snapshot.
1718 CGFloat newPageOffset = 0;
1719 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191720 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1721 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131722 animationParentView = self.view;
1723 newPage = [self pageFullScreenOpenCloseAnimationView];
1724 } else {
1725 newPage = [self pageOpenCloseAnimationView];
1726 }
1727 newPageOffset = newPage.frame.origin.y;
1728
1729 [tab view].frame = _contentArea.bounds;
1730 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1731 [animationParentView addSubview:newPage];
1732 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521733 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131734 newPage, -newPageOffset,
1735 newPage.frame.size.height - newPage.image.size.height, origin,
1736 _isOffTheRecord, NULL, ^{
1737 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121738 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421739 // Use the model's currentTab here because it is possible that it can
1740 // be reset to a new value before the new Tab animation finished (e.g.
1741 // if another Tab shows a dialog via |dialogPresenter|). However, that
1742 // tab's view hasn't been displayed yet because it was in a new tab
1743 // animation.
1744 Tab* currentTab = [_model currentTab];
1745 if (currentTab) {
1746 [self tabSelected:currentTab];
1747 }
sdefresnee65fd872016-12-19 13:38:131748 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391749
1750 if (self.foregroundTabWasAddedCompletionBlock) {
1751 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451752 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391753 }
sdefresnee65fd872016-12-19 13:38:131754 });
1755 } else {
1756 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1757 // snapshot before adding the views needed for the background animation.
1758 Tab* topTab = [_model currentTab];
1759 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1760 visibleFrameOnly:self.isToolbarOnScreen];
1761 // Add three layers in order on top of the contentArea for the animation:
1762 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271763 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131764 InstallBackgroundInView(background);
1765 [_contentArea addSubview:background];
1766
1767 // 2. A CardView displaying the data from the current tab.
1768 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1769 NSString* title = [topTab title];
1770 if (![title length])
1771 title = [topTab urlDisplayString];
1772 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131773 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371774 [topCard setFavicon:nil];
1775
1776 favicon::FaviconDriver* faviconDriver =
1777 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1778 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1779 gfx::Image favicon = faviconDriver->GetFavicon();
1780 if (!favicon.IsEmpty())
1781 [topCard setFavicon:favicon.ToUIImage()];
1782 }
sdefresnee65fd872016-12-19 13:38:131783
1784 // 3. A new, blank CardView to represent the new tab being added.
1785 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521786 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131787 topCard, [_contentArea frame], IsPortrait(), ^{
1788 [background removeFromSuperview];
1789 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121790 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131791 // Resnapshot the top card if it has its own toolbar, as the toolbar
1792 // will be captured in the new tab animation, but isn't desired for
1793 // the stack view snapshots.
1794 id nativeController = [self nativeControllerForTab:topTab];
1795 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1796 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1797 startVoiceSearchIfNecessaryBlock();
1798 });
peterlaurens9f1b6e02017-06-22 17:46:451799 // Reset the foreground tab completion block so that it can never be
1800 // called more than once regardless of foreground/background tab
1801 // appearances.
1802 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131803 }
1804}
1805
1806#pragma mark - UI Configuration and Layout
1807
1808- (void)updateWithTabModel:(TabModel*)model
1809 browserState:(ios::ChromeBrowserState*)browserState {
1810 DCHECK(model);
1811 DCHECK(browserState);
1812 DCHECK(!_model);
1813 DCHECK(!_browserState);
1814 _browserState = browserState;
1815 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271816 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381817
sdefresnee65fd872016-12-19 13:38:131818 [_model addObserver:self];
1819
1820 if (!_isOffTheRecord) {
1821 [DefaultIOSWebViewFactory
1822 registerWebViewFactory:[ChromeWebViewFactory class]];
1823 }
1824 NSUInteger count = [_model count];
1825 for (NSUInteger index = 0; index < count; ++index)
1826 [self installDelegatesForTab:[_model tabAtIndex:index]];
1827
1828 [self registerForNotifications];
1829
gambardbdc07cc2017-02-03 16:43:111830 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121831 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271832 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131833
sdefresnedc432f42017-01-17 14:36:591834 // Register for bookmark changed notification (BookmarkModel may be null
1835 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131836 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591837 if (_bookmarkModel) {
1838 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1839 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1840 }
sdefresnee65fd872016-12-19 13:38:131841}
1842
sdefresnee65fd872016-12-19 13:38:131843- (void)browserStateDestroyed {
1844 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131845 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271846 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461847 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131848 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381849
1850 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431851 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111852 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381853 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271854 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351855 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031856 [self.tabStripCoordinator stop];
1857 self.tabStripCoordinator = nil;
1858 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381859
sdefresnee65fd872016-12-19 13:38:131860 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391861 [_dispatcher stopDispatchingToTarget:self];
1862 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131863}
1864
1865- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221866 CGFloat statusBarHeight = StatusBarHeight();
1867 CGRect statusBarFrame =
1868 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1869 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1870 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131871 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221872 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1873 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1874 [_fakeStatusBarView layer].zPosition = 99;
1875 [[self view] addSubview:_fakeStatusBarView];
1876 } else {
1877 // Add a white bar on phone so that the status bar on the NTP is white.
1878 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1879 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131880 }
1881}
1882
1883// Create the UI elements. May or may not have valid browser state & tab model.
1884- (void)buildToolbarAndTabStrip {
1885 DCHECK([self isViewLoaded]);
1886 DCHECK(!_toolbarModelDelegate);
1887
Rohit Rao44f204302017-08-10 14:49:541888 // Initialize the prerender service before creating the toolbar controller.
1889 PrerenderService* prerenderService =
1890 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1891 if (prerenderService) {
1892 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131893 }
1894
1895 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201896 _toolbarModelDelegate.reset(
1897 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131898 _toolbarModelIOS.reset([_dependencyFactory
1899 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
sczsf1620e52017-10-02 22:54:461900 _toolbarCoordinator =
1901 [[LegacyToolbarCoordinator alloc] initWithBaseViewController:self];
Gauthier Ambard29939db12017-10-30 16:47:311902 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sczsf1620e52017-10-02 22:54:461903 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051904 [_toolbarCoordinator
1905 setWebToolbar:[_dependencyFactory
1906 newWebToolbarControllerWithDelegate:self
1907 urlLoader:self
1908 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461909 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391910 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461911 [_toolbarCoordinator setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271912 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461913 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051914 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131915
sdefresnee65fd872016-12-19 13:38:131916 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031917 self.tabStripCoordinator =
1918 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1919 self.tabStripCoordinator.browserState = _browserState;
1920 self.tabStripCoordinator.dispatcher = _dispatcher;
1921 self.tabStripCoordinator.tabModel = _model;
1922 self.tabStripCoordinator.presentationProvider = self;
1923 self.tabStripCoordinator.animationWaitDuration =
1924 kFullScreenControllerToolbarAnimationDuration;
1925 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131926 }
1927
1928 // Create infobar container.
1929 if (!_infoBarContainerDelegate) {
1930 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1931 _infoBarContainer.reset(
1932 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1933 }
1934}
1935
Jean-François Geyelined4cde72017-10-11 11:34:501936- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111937 NSLayoutYAxisAnchor* topAnchor;
1938 // On iPad, the toolbar is underneath the tab strip.
1939 // On iPhone, it is underneath the top of the screen.
1940 if (IsIPadIdiom()) {
1941 topAnchor = self.tabStripView.bottomAnchor;
1942 } else {
1943 topAnchor = [self view].topAnchor;
1944 }
1945
Gauthier Ambard100670f72017-10-27 09:54:271946 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501947
1948 [NSLayoutConstraint activateConstraints:@[
1949 [[_toolbarCoordinator view].leadingAnchor
1950 constraintEqualToAnchor:[self view].leadingAnchor],
Jean-François Geyelince0a4742017-10-25 12:34:111951 [[_toolbarCoordinator view].topAnchor constraintEqualToAnchor:topAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501952 [[_toolbarCoordinator view].trailingAnchor
1953 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501954 ]];
1955 [[self view] layoutIfNeeded];
1956}
1957
sdefresnee65fd872016-12-19 13:38:131958// Enable functionality that only makes sense if the views are loaded and
1959// both browser state and tab model are valid.
1960- (void)addUIFunctionalityForModelAndBrowserState {
1961 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051962 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131963 DCHECK(_model);
1964 DCHECK([self isViewLoaded]);
1965
1966 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1967
Rohit Raoaf46af92017-08-10 12:52:301968 infobars::InfoBarManager* infoBarManager = nullptr;
1969 if (_model.currentTab) {
1970 DCHECK(_model.currentTab.webState);
1971 infoBarManager =
1972 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1973 }
sdefresnee65fd872016-12-19 13:38:131974 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1975
sczsdd860eba2017-08-10 01:55:381976 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431977 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1978 initWithBaseViewController:self];
1979 _activityServiceCoordinator.dispatcher = _dispatcher;
1980 _activityServiceCoordinator.tabModel = _model;
1981 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461982 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051983 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:431984 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:431985
Rohit Raocda0a992017-08-16 15:37:111986 _qrScannerCoordinator =
1987 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1988 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:051989 _qrScannerCoordinator.loadProvider =
1990 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:111991 _qrScannerCoordinator.presentationProvider = self;
1992
sczsdd860eba2017-08-10 01:55:381993 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131994 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381995 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:461996 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051997 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:381998 _tabHistoryCoordinator.tabModel = _model;
1999 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462000 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:052001 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382002
sczs6ae47ad2017-09-06 17:26:532003 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202004 _sadTabCoordinator.baseViewController = self;
2005 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532006
Gregory Chatzinoffdf93d692017-09-09 01:32:272007 _pageInfoCoordinator =
2008 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
2009 _pageInfoCoordinator.browserState = _browserState;
2010 _pageInfoCoordinator.dispatcher = _dispatcher;
2011 _pageInfoCoordinator.loader = self;
2012 _pageInfoCoordinator.presentationProvider = self;
2013 _pageInfoCoordinator.tabModel = _model;
2014
Louis Romerod11747a2017-10-20 20:10:352015 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2016 _externalSearchCoordinator.dispatcher = _dispatcher;
2017
mathp9b4c11d2017-07-06 20:24:132018 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272019 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132020 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202021 browserState:_browserState
2022 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052023 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192024 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132025 }
2026}
2027
2028// Set the frame for the various views. View must be loaded.
2029- (void)setUpViewLayout {
2030 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132031 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132032 CGFloat minY = [self headerOffset];
2033
Justin Cohenb3170c32017-09-19 01:55:222034 // Update the fake toolbar background height.
2035 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2036 fakeStatusBarFrame.size.height = StatusBarHeight();
2037 _fakeStatusBarView.frame = fakeStatusBarFrame;
2038
edchinf5150c682017-09-18 02:50:032039 if (self.tabStripView) {
2040 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132041 }
2042
2043 // Position the toolbar next, either at the top of the browser view or
2044 // directly under the tabstrip.
sczsf1620e52017-10-02 22:54:462045 CGRect toolbarFrame = [[_toolbarCoordinator view] frame];
sdefresnee65fd872016-12-19 13:38:132046 toolbarFrame.origin = CGPointMake(0, minY);
2047 toolbarFrame.size.width = widthOfView;
Jean-François Geyelined4cde72017-10-11 11:34:502048 if (!base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
2049 [[_toolbarCoordinator view] setFrame:toolbarFrame];
2050 }
sdefresnee65fd872016-12-19 13:38:132051
2052 // Place the infobar container above the content area.
2053 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
2054 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
2055
2056 // Place the toolbar controller above the infobar container.
sczsf1620e52017-10-02 22:54:462057 [[self view] insertSubview:[_toolbarCoordinator view]
sdefresnee65fd872016-12-19 13:38:132058 aboveSubview:infoBarContainerView];
2059 minY += CGRectGetHeight(toolbarFrame);
2060
2061 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2062 // content slightly.
sczs8c837782017-10-03 02:57:242063 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132064
2065 // Adjust the content area to be under the toolbar, for fullscreen or below
2066 // the toolbar is not fullscreen.
2067 CGRect contentFrame = [_contentArea frame];
2068 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222069 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2070 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132071 [_contentArea setFrame:contentFrame];
2072
2073 // Adjust the infobar container to be either at the bottom of the screen
2074 // (iPhone) or on the lower toolbar edge (iPad).
2075 CGRect infoBarFrame = contentFrame;
2076 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2077 infoBarFrame.size.height = 0;
2078 [infoBarContainerView setFrame:infoBarFrame];
2079
2080 // Attach the typing shield to the content area but have it hidden.
2081 [_typingShield setFrame:[_contentArea frame]];
2082 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
2083 [_typingShield setHidden:YES];
2084 _typingShield.accessibilityIdentifier = @"Typing Shield";
2085 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
2086}
2087
sdefresnee65fd872016-12-19 13:38:132088- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2089 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442090 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132091
kkhorimotoa44349c12017-04-12 23:02:122092 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132093 // Hide findbar. |updateToolbar| will restore the findbar later.
2094 [self hideFindBarWithAnimation:NO];
2095
2096 // Make new content visible, resizing it first as the orientation may
2097 // have changed from the last time it was displayed.
2098 [[tab view] setFrame:_contentArea.bounds];
2099 [_contentArea displayContentView:[tab view]];
2100 }
2101 [self updateToolbar];
2102
2103 if (newSelection)
sczsf1620e52017-10-02 22:54:462104 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132105
2106 // Notify the Tab that it was displayed.
2107 [tab wasShown];
2108}
2109
2110- (void)initializeBookmarkInteractionController {
2111 if (_bookmarkInteractionController)
2112 return;
edchinbb8ba892017-09-12 15:44:032113 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2114 initWithBrowserState:_browserState
2115 loader:self
2116 parentController:self
2117 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132118}
2119
2120// Update the state of back and forward buttons, hiding the forward button if
2121// there is nowhere to go. Assumes the model's current tab is up to date.
2122- (void)updateToolbar {
2123 // If the BVC has been partially torn down for low memory, wait for the
2124 // view rebuild to handle toolbar updates.
2125 if (!(_toolbarModelIOS && _browserState))
2126 return;
2127
2128 Tab* tab = [_model currentTab];
2129 if (![tab navigationManager])
2130 return;
sczsf1620e52017-10-02 22:54:462131 [_toolbarCoordinator updateToolbarState];
2132 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132133
Rohit Rao44f204302017-08-10 14:49:542134 PrerenderService* prerenderService =
2135 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2136 BOOL isPrerenderTab =
2137 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2138 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462139 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132140
2141 // Also update the loading state for the tools menu (that is really an
2142 // extension of the toolbar on the iPhone).
2143 if (!IsIPadIdiom())
sczsf1620e52017-10-02 22:54:462144 [[_toolbarCoordinator toolsPopupController]
sdefresnee65fd872016-12-19 13:38:132145 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2146
rohitrao005a6432017-03-16 20:52:422147 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2148 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132149 [self showFindBarWithAnimation:NO
2150 selectText:YES
2151 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362152 }
sdefresnee65fd872016-12-19 13:38:132153
2154 // Hide the toolbar if displaying phone NTP.
2155 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552156 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132157 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552158 if (item) {
2159 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132160 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2161 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462162 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2163 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132164 }
sczsf1620e52017-10-02 22:54:462165 [[_toolbarCoordinator view] setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132166 }
2167}
2168
2169- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122170 self.dialogPresenter.active =
2171 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132172}
2173
2174- (void)dismissPopups {
sczsf1620e52017-10-02 22:54:462175 [_toolbarCoordinator dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272176 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282177 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472178 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382179}
2180
Cooper Knaakd0a974cd2017-08-10 18:05:472181- (BubbleViewControllerPresenter*)
2182bubblePresenterForFeature:(const base::Feature&)feature
2183 direction:(BubbleArrowDirection)direction
2184 alignment:(BubbleAlignment)alignment
2185 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212186 DCHECK(self.browserState);
2187 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382188 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472189 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382190 }
2191 // Capture |weakSelf| instead of the feature engagement tracker object
2192 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2193 // the feature engagement tracker will remain pointing to invalid memory if
2194 // its owner (the ChromeBrowserState) is deallocated.
2195 __weak BrowserViewController* weakSelf = self;
2196 void (^dismissalCallback)(void) = ^() {
2197 BrowserViewController* strongSelf = weakSelf;
2198 if (strongSelf) {
2199 feature_engagement::TrackerFactory::GetForBrowserState(
2200 strongSelf.browserState)
2201 ->Dismissed(feature);
2202 }
2203 };
2204
Cooper Knaakd0a974cd2017-08-10 18:05:472205 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382206 [[BubbleViewControllerPresenter alloc] initWithText:text
2207 arrowDirection:direction
2208 alignment:alignment
2209 dismissalCallback:dismissalCallback];
2210
Cooper Knaakd0a974cd2017-08-10 18:05:472211 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132212}
2213
Cooper Knaak120cee5e2017-08-10 20:57:002214- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212215 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002216 // If the tab tip bubble has already been presented and the user is still
2217 // considered engaged, it can't be overwritten or set to |nil| or else it will
2218 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2219 // can be safely overwritten or set to |nil|.
2220 if (!self.tabTipBubblePresenter.isUserEngaged) {
2221 __weak BrowserViewController* weakSelf = self;
2222 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2223 [weakSelf presentNewTabTipBubble];
2224 };
2225
2226 // Because the new tab tip occurs on startup, the feature engagement
2227 // tracker's database is not guaranteed to be loaded by this time. For the
2228 // bubble to appear properly, a callback is used to guarantee the event data
2229 // is loaded before the check to see if the promotion should be displayed.
2230 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2231 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2232 }
2233}
2234
2235- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212236 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002237 NSString* text =
2238 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2239 CGPoint tabSwitcherAnchor;
2240 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032241 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002242 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032243 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002244 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2245 } else {
sczsf1620e52017-10-02 22:54:462246 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002247 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462248 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002249 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2250 }
Cooper Knaake963d6702017-08-11 21:03:112251 // If the feature engagement tracker does not consider it valid to display
2252 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2253 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002254 self.tabTipBubblePresenter =
2255 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2256 direction:BubbleArrowDirectionUp
2257 alignment:BubbleAlignmentTrailing
2258 text:text];
2259 [self.tabTipBubblePresenter presentInViewController:self
2260 view:self.view
2261 anchorPoint:tabSwitcherAnchor];
2262}
2263
Helen Yang9175bd52017-08-12 00:28:402264- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212265 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402266 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2267 // user is still considered engaged.
2268 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2269 __weak BrowserViewController* weakSelf = self;
2270 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2271 [weakSelf presentNewIncognitoTabTipBubble];
2272 };
2273
2274 // Use a callback in case the new incognito tab tip should be shown on
2275 // startup. This ensures that the tracker's database will be fully loaded
2276 // before checking if the promotion should be displayed.
2277 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2278 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2279 }
2280}
2281
2282- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212283 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462284 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402285 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2286 NSString* text = l10n_util::GetNSStringWithFixup(
2287 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462288 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402289 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2290 self.incognitoTabTipBubblePresenter =
2291 [self bubblePresenterForFeature:feature_engagement::
2292 kIPHNewIncognitoTabTipFeature
2293 direction:BubbleArrowDirectionUp
2294 alignment:BubbleAlignmentTrailing
2295 text:text];
2296 [self.incognitoTabTipBubblePresenter
2297 presentInViewController:self
2298 view:self.view
2299 anchorPoint:toolsButtonAnchor];
2300 // Only trigger the tools menu button animation if the bubble is shown.
2301 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462302 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402303 }
2304}
2305
sdefresnee65fd872016-12-19 13:38:132306#pragma mark - Tap handling
2307
Mark Cogandfcdea72017-07-18 13:47:382308- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032309 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2310 _lastTapPoint = CGPointZero;
2311 } else {
2312 _lastTapPoint =
2313 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132314 }
Mark Cogane01ebce2017-07-12 19:31:032315 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132316}
2317
2318- (CGPoint)lastTapPoint {
2319 if (CACurrentMediaTime() - _lastTapTime < 1) {
2320 return _lastTapPoint;
2321 }
2322 return CGPointZero;
2323}
2324
2325- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2326 UIView* view = gestureRecognizer.view;
2327 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2328 _lastTapPoint =
2329 [[view superview] convertPoint:viewCoordinate toView:self.view];
2330 _lastTapTime = CACurrentMediaTime();
2331}
2332
2333- (BOOL)addTabIfNoTabWithNormalBrowserState {
2334 if (![_model count]) {
2335 if (!_isOffTheRecord) {
2336 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2337 transition:ui::PAGE_TRANSITION_TYPED];
2338 return YES;
2339 }
2340 }
2341 return NO;
2342}
2343
2344#pragma mark - Tab creation and selection
2345
2346// Called when either a tab finishes loading or when a tab with finished content
2347// is added directly to the model via pre-rendering.
2348- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2349 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2350
2351 // Persist the session on a delay.
2352 [_model saveSessionImmediately:NO];
2353}
2354
2355- (Tab*)addSelectedTabWithURL:(const GURL&)url
2356 postData:(TemplateURLRef::PostContent*)postData
2357 transition:(ui::PageTransition)transition {
2358 return [self addSelectedTabWithURL:url
2359 postData:postData
2360 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492361 transition:transition
2362 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132363}
2364
2365- (Tab*)addSelectedTabWithURL:(const GURL&)url
2366 transition:(ui::PageTransition)transition {
2367 return [self addSelectedTabWithURL:url
2368 atIndex:[_model count]
2369 transition:transition];
2370}
2371
2372- (Tab*)addSelectedTabWithURL:(const GURL&)url
2373 atIndex:(NSUInteger)position
2374 transition:(ui::PageTransition)transition {
2375 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492376 atIndex:position
2377 transition:transition
2378 tabAddedCompletion:nil];
2379}
2380
2381- (Tab*)addSelectedTabWithURL:(const GURL&)url
2382 atIndex:(NSUInteger)position
2383 transition:(ui::PageTransition)transition
2384 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2385 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132386 postData:NULL
2387 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492388 transition:transition
2389 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132390}
2391
2392- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2393 postData:(TemplateURLRef::PostContent*)postData
2394 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492395 transition:(ui::PageTransition)transition
2396 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132397 if (position == NSNotFound)
2398 position = [_model count];
2399 DCHECK(position <= [_model count]);
2400
2401 web::NavigationManager::WebLoadParams params(URL);
2402 params.transition_type = transition;
2403 if (postData) {
2404 // Extract the content type and post params from |postData| and add them
2405 // to the load params.
2406 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2407 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2408 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172409 params.post_data.reset(data);
2410 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132411 }
Olivier Robind508a5632017-07-19 16:29:492412
2413 if (tabAddedCompletion) {
2414 if (self.foregroundTabWasAddedCompletionBlock) {
2415 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2416 self.foregroundTabWasAddedCompletionBlock;
2417 self.foregroundTabWasAddedCompletionBlock = ^{
2418 oldForegroundTabWasAddedCompletionBlock();
2419 tabAddedCompletion();
2420 };
2421 } else {
2422 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2423 }
2424 }
2425
sdefresnea6395912017-03-01 01:14:352426 Tab* tab = [_model insertTabWithLoadParams:params
2427 opener:nil
2428 openedByDOM:NO
2429 atIndex:position
2430 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132431 return tab;
2432}
2433
olivierrobin889af53f2017-03-01 14:56:322434// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132435- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322436 web::WebState* webState = tab.webState;
2437 if (!webState)
2438 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392439 web::NavigationItem* visibleItem =
2440 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322441 if (!visibleItem)
2442 return NO;
2443 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132444}
2445
2446- (void)expectNewForegroundTab {
2447 _expectingForegroundTab = YES;
2448}
2449
2450- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2451 CGRect viewBounds, remainder;
2452 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2453 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172454 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132455}
2456
2457- (UIImageView*)pageOpenCloseAnimationView {
2458 CGRect frame = [_contentArea bounds];
2459
2460 frame.size.height = frame.size.height - [self headerHeight];
2461 frame.origin.y = [self headerHeight];
2462
stkhapuginf58b10d02017-04-10 13:36:172463 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132464 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2465 pageView.center = center;
2466
2467 pageView.backgroundColor = [UIColor whiteColor];
2468 return pageView;
2469}
2470
2471- (void)installDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112472 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
sdefresne49cf2862017-03-15 13:46:142473 // Unregistration happens when the Tab is removed from the TabModel.
Mike Doughertya1ec26402017-08-23 19:46:312474 tab.iOSCaptivePortalBlockingPageDelegate = self;
edchincd32fdf2017-10-25 12:45:452475
2476 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2477 if (PasswordTabHelper* passwordTabHelper =
2478 PasswordTabHelper::FromWebState(tab.webState)) {
2479 passwordTabHelper->SetDispatcher(self.dispatcher);
2480 passwordTabHelper->SetPasswordControllerDelegate(self);
2481 }
2482
sdefresnee65fd872016-12-19 13:38:132483 tab.dialogDelegate = self;
2484 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132485 tab.passKitDialogProvider = self;
Kurt Horimoto803840622017-10-28 01:20:372486 if (!base::FeatureList::IsEnabled(features::kNewFullscreen)) {
2487 tab.fullScreenControllerDelegate = self;
2488 }
sdefresnee65fd872016-12-19 13:38:132489 if (!IsIPadIdiom()) {
2490 tab.overscrollActionsControllerDelegate = self;
2491 }
olivierrobin9ce77b82017-01-12 17:29:192492 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132493 tab.tabSnapshottingDelegate = self;
2494 // Install the proper CRWWebController delegates.
2495 tab.webController.nativeProvider = self;
2496 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512497 // BrowserViewController presents SKStoreKitViewController on behalf of a
2498 // tab.
2499 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2500 if (tabHelper)
2501 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132502 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532503 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292504 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532505 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292506 }
Sylvain Defresnecacc3a52017-09-12 13:51:042507 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592508 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572509 NetExportTabHelper::CreateForWebState(tab.webState, self);
edchincd32fdf2017-10-25 12:45:452510
2511 if (AccountConsistencyService* accountConsistencyService =
2512 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2513 self.browserState)) {
2514 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552515 }
sdefresnee65fd872016-12-19 13:38:132516}
2517
sdefresne49cf2862017-03-15 13:46:142518- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112519 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
Mike Doughertya1ec26402017-08-23 19:46:312520 tab.iOSCaptivePortalBlockingPageDelegate = nil;
edchincd32fdf2017-10-25 12:45:452521
2522 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2523 if (PasswordTabHelper* passwordTabHelper =
2524 PasswordTabHelper::FromWebState(tab.webState))
2525 passwordTabHelper->SetDispatcher(nil);
2526
sdefresne49cf2862017-03-15 13:46:142527 tab.dialogDelegate = nil;
2528 tab.snapshotOverlayProvider = nil;
2529 tab.passKitDialogProvider = nil;
Kurt Horimoto803840622017-10-28 01:20:372530 if (!base::FeatureList::IsEnabled(features::kNewFullscreen)) {
2531 tab.fullScreenControllerDelegate = nil;
2532 }
sdefresne49cf2862017-03-15 13:46:142533 if (!IsIPadIdiom()) {
2534 tab.overscrollActionsControllerDelegate = nil;
2535 }
2536 tab.tabHeadersDelegate = nil;
2537 tab.tabSnapshottingDelegate = nil;
2538 tab.webController.nativeProvider = nil;
2539 tab.webController.swipeRecognizerProvider = nil;
2540 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2541 if (tabHelper)
2542 tabHelper->SetLauncher(nil);
2543 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452544 if (AccountConsistencyService* accountConsistencyService =
2545 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2546 self.browserState)) {
2547 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2548 }
sdefresne49cf2862017-03-15 13:46:142549}
2550
sdefresnee65fd872016-12-19 13:38:132551// Called when a tab is selected in the model. Make any required view changes.
2552// The notification will not be sent when the tab is already the selected tab.
2553- (void)tabSelected:(Tab*)tab {
2554 DCHECK(tab);
2555
2556 // Ignore changes while the tab stack view is visible (or while suspended).
2557 // The display will be refreshed when this view becomes active again.
2558 if (!self.visible || ![_model webUsageEnabled])
2559 return;
2560
2561 [self displayTab:tab isNewSelection:YES];
2562
kkhorimotoa44349c12017-04-12 23:02:122563 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132564 // Now that the new tab has been displayed, return to normal. Rather than
2565 // keep a reference to the previous tab, just turn off preview mode for all
2566 // tabs (since doing so is a no-op for the tabs that don't have it set).
2567 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272568 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132569 [tab.webController setOverlayPreviewMode:NO];
2570 }
2571 }
2572}
2573
edchinf5150c682017-09-18 02:50:032574- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2575 return [self.tabStripCoordinator placeholderView];
2576}
2577
Sylvain Defresne41170aa2017-06-15 10:25:202578- (void)shutdown {
2579 DCHECK(!_isShutdown);
2580 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032581 [self.tabStripCoordinator stop];
2582 self.tabStripCoordinator = nil;
2583 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202584 _infoBarContainer = nil;
2585 _readingListMenuNotifier = nil;
2586 if (_bookmarkModel)
2587 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2588 [_model removeObserver:self];
2589 [[UpgradeCenter sharedInstance] unregisterClient:self];
2590 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052591 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202592 if (_voiceSearchController)
2593 _voiceSearchController->SetDelegate(nil);
2594 [_rateThisAppDialog setDelegate:nil];
2595 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462596 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202597}
2598
sdefresnee65fd872016-12-19 13:38:132599#pragma mark - SnapshotOverlayProvider methods
2600
2601- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2602 NSMutableArray* overlays = [NSMutableArray array];
2603 if (![_model webUsageEnabled]) {
2604 return overlays;
2605 }
2606 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2607 if (voiceSearchView) {
2608 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272609 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132610 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272611 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132612 [overlays addObject:voiceSearchOverlay];
2613 }
2614 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2615 if (infoBarView) {
2616 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272617 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132618 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272619 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132620 [overlays addObject:infoBarOverlay];
2621 }
2622 return overlays;
2623}
2624
2625#pragma mark -
2626
2627- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2628 if (IsIPadIdiom()) {
2629 // Not using overlays on iPad because the content is pushed down by
2630 // infobar and the transition between snapshot and fresh page can
2631 // cause both snapshot and real infobars to appear at the same time.
2632 return nil;
2633 }
2634 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302635 if (currentTab && tab == currentTab) {
2636 DCHECK(currentTab.webState);
2637 infobars::InfoBarManager* infoBarManager =
2638 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132639 if (infoBarManager->infobar_count() > 0) {
2640 DCHECK(_infoBarContainer);
2641 return _infoBarContainer->view();
2642 }
2643 }
2644 return nil;
2645}
2646
2647- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272648 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132649 // There is no UI representation for non-current tabs or there is
2650 // no _infoBarContainer instantiated yet.
2651 // Return offset outside of tab.
2652 return CGRectGetMaxY(self.view.frame);
2653 } else if (IsIPadIdiom()) {
2654 // The infobars on iPad are display at the top of a tab.
2655 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2656 } else {
2657 // The infobars on iPhone are displayed at the bottom of a tab.
2658 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2659 return CGRectGetMaxY(visibleFrame) -
2660 CGRectGetHeight(_infoBarContainer->view().frame);
2661 }
2662}
2663
2664- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2665 Tab* currentTab = [_model currentTab];
2666 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2667 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2668 return _voiceSearchBar;
2669 }
2670 return nil;
2671}
2672
2673- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2674 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2675 // There is no UI representation for non-current tabs or there is
2676 // no visible voice search. Return offset outside of tab.
2677 return CGRectGetMaxY(self.view.frame);
2678 } else {
2679 // The voice search bar on iPhone is displayed at the bottom of a tab.
2680 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2681 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2682 }
2683}
2684
2685- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272686 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132687 VoiceSearchProvider* provider =
2688 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2689 if (provider) {
2690 _voiceSearchController =
2691 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462692 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052693 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132694 }
2695 }
2696}
2697
2698- (void)ensureVoiceSearchBarCreated {
2699 if (_voiceSearchBar)
2700 return;
2701
2702 CGFloat width = CGRectGetWidth([[self view] bounds]);
2703 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2704 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272705 _voiceSearchBar = ios::GetChromeBrowserProvider()
2706 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002707 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132708 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2709 [_voiceSearchBar setHidden:YES];
2710 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2711 UIViewAutoresizingFlexibleWidth];
2712 [self.view insertSubview:_voiceSearchBar
2713 belowSubview:_infoBarContainer->view()];
2714}
2715
2716- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2717 // Voice search bar exists and is shown/hidden.
2718 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272719 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132720 return;
2721
2722 // Voice search bar doesn't exist and thus is not visible.
2723 if (!_voiceSearchBar && !show)
2724 return;
2725
2726 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272727 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132728 else
stkhapuginc9eee7b2017-04-10 15:49:272729 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132730}
2731
2732- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2733 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2734 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2735 self.shouldShowVoiceSearchBar) {
2736 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2737 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272738 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132739 }
2740 id currentNativeController =
2741 [self nativeControllerForTab:self.tabModel.currentTab];
2742 Protocol* possibleOwnerProtocol =
2743 @protocol(LogoAnimationControllerOwnerOwner);
2744 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2745 [currentNativeController logoAnimationControllerOwner]) {
2746 // If the current native controller is showing a GLIF view (e.g. the NTP
2747 // when there is no doodle), use that GLIFControllerOwner.
2748 return [currentNativeController logoAnimationControllerOwner];
2749 }
2750 return nil;
2751}
2752
2753#pragma mark - PassKitDialogProvider methods
2754
2755- (void)presentPassKitDialog:(NSData*)data {
2756 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272757 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132758 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272759 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132760 if (error || !data) {
2761 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302762 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132763 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302764 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132765 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2766 // NULL, replace if with DCHECK).
2767 if (infoBarManager)
2768 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2769 }
2770 } else {
2771 PKAddPassesViewController* passKitViewController =
2772 [_dependencyFactory newPassKitViewControllerForPass:pass];
2773 if (passKitViewController) {
2774 [self presentViewController:passKitViewController
2775 animated:YES
2776 completion:^{
2777 }];
2778 }
2779 }
2780}
2781
2782- (UIStatusBarStyle)preferredStatusBarStyle {
2783 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2784 : UIStatusBarStyleDefault;
2785}
2786
Tomasz Garbusb844e992017-09-29 12:44:552787#pragma mark - PasswordControllerDelegate methods
2788
2789- (BOOL)displaySignInNotification:(UIViewController*)viewController
2790 fromTabId:(NSString*)tabId {
2791 // Check if the call comes from currently visible tab.
2792 if ([tabId isEqual:[_model currentTab].tabId]) {
2793 [self addChildViewController:viewController];
2794 [self.view addSubview:viewController.view];
2795 [viewController didMoveToParentViewController:self];
2796 return YES;
2797 } else {
2798 return NO;
2799 }
2800}
2801
sdefresnee65fd872016-12-19 13:38:132802#pragma mark - CRWWebStateDelegate methods.
2803
eugenebut75a06fa72017-01-09 17:09:552804- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512805 createNewWebStateForURL:(const GURL&)URL
2806 openerURL:(const GURL&)openerURL
2807 initiatedByUser:(BOOL)initiatedByUser {
2808 // Check if requested web state is a popup and block it if necessary.
2809 if (!initiatedByUser) {
2810 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2811 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102812 // It's possible for a page to inject a popup into a window created via
2813 // window.open before its initial load is committed. Rather than relying
2814 // on the last committed or pending NavigationItem's referrer policy, just
2815 // use ReferrerPolicyDefault.
2816 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2817 // policy once referrer policies are correctly recorded in
2818 // NavigationItems.
2819 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512820 helper->HandlePopup(URL, referrer);
2821 return nil;
2822 }
2823 }
2824
2825 // Requested web state should not be blocked from opening.
2826 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2827 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2828
2829 // Tabs open by DOM are always renderer initiated.
2830 web::NavigationManager::WebLoadParams params(GURL{});
2831 params.transition_type = ui::PAGE_TRANSITION_LINK;
2832 params.is_renderer_initiated = true;
2833 Tab* childTab = [[self tabModel]
2834 insertTabWithLoadParams:params
2835 opener:currentTab
2836 openedByDOM:YES
2837 atIndex:TabModelConstants::kTabPositionAutomatically
2838 inBackground:NO];
2839 return childTab.webState;
2840}
2841
eugenebutb46b2122017-03-14 02:43:262842- (void)closeWebState:(web::WebState*)webState {
2843 // Only allow a web page to close itself if it was opened by DOM, or if there
2844 // are no navigation items.
2845 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582846 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262847
2848 if (![self tabModel])
2849 return;
2850
2851 NSUInteger index = [[self tabModel] indexOfTab:tab];
2852 if (index != NSNotFound)
2853 [[self tabModel] closeTabAtIndex:index];
2854}
2855
eugenebut275f5892017-03-09 22:20:512856- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552857 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2858 switch (params.disposition) {
2859 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2860 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2861 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352862 insertTabWithURL:params.url
2863 referrer:params.referrer
2864 transition:params.transition
2865 opener:LegacyTabHelper::GetTabForWebState(webState)
2866 openedByDOM:NO
2867 atIndex:TabModelConstants::kTabPositionAutomatically
2868 inBackground:(params.disposition ==
2869 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552870 return tab.webState;
2871 }
2872 case WindowOpenDisposition::CURRENT_TAB: {
2873 web::NavigationManager::WebLoadParams loadParams(params.url);
2874 loadParams.referrer = params.referrer;
2875 loadParams.transition_type = params.transition;
2876 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2877 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2878 return webState;
2879 }
eugenebutd0984e82017-02-22 23:47:512880 case WindowOpenDisposition::NEW_POPUP: {
2881 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352882 insertTabWithURL:params.url
2883 referrer:params.referrer
2884 transition:params.transition
2885 opener:LegacyTabHelper::GetTabForWebState(webState)
2886 openedByDOM:YES
2887 atIndex:TabModelConstants::kTabPositionAutomatically
2888 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512889 return tab.webState;
2890 }
eugenebut75a06fa72017-01-09 17:09:552891 default:
2892 NOTIMPLEMENTED();
2893 return nullptr;
2894 };
2895}
2896
Mike Dougherty4e6b3a32017-08-23 18:49:212897- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132898 handleContextMenu:(const web::ContextMenuParams&)params {
2899 // Prevent context menu from displaying for a tab which is no longer the
2900 // current one.
2901 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212902 return;
sdefresnee65fd872016-12-19 13:38:132903 }
2904
2905 // No custom context menu if no valid url is available in |params|.
2906 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212907 return;
sdefresnee65fd872016-12-19 13:38:132908 }
2909
2910 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132911
stkhapuginc9eee7b2017-04-10 15:49:272912 _contextMenuCoordinator =
2913 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2914 params:params];
sdefresnee65fd872016-12-19 13:38:132915
2916 NSString* title = nil;
2917 ProceduralBlock action = nil;
2918
stkhapuginc9eee7b2017-04-10 15:49:272919 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132920 GURL link = params.link_url;
2921 bool isLink = link.is_valid();
2922 GURL imageUrl = params.src_url;
2923 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192924 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132925
2926 if (isLink) {
2927 if (link.SchemeIs(url::kJavaScriptScheme)) {
2928 // Open
2929 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2930 action = ^{
2931 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2932 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2933 };
2934 [_contextMenuCoordinator addItemWithTitle:title action:action];
2935 }
2936
2937 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192938 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132939
sdefresnee65fd872016-12-19 13:38:132940 // Open in New Tab.
2941 title = l10n_util::GetNSStringWithFixup(
2942 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2943 action = ^{
2944 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002945 // The "New Tab" item in the context menu opens a new tab in the current
2946 // browser state. |isOffTheRecord| indicates whether or not the current
2947 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132948 [weakSelf webPageOrderedOpen:link
2949 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002950 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132951 inBackground:YES
2952 appendTo:kCurrentTab];
2953 };
2954 [_contextMenuCoordinator addItemWithTitle:title action:action];
2955 if (!_isOffTheRecord) {
2956 // Open in Incognito Tab.
2957 title = l10n_util::GetNSStringWithFixup(
2958 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2959 action = ^{
2960 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2961 [weakSelf webPageOrderedOpen:link
2962 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132963 inIncognito:YES
2964 inBackground:NO
2965 appendTo:kCurrentTab];
2966 };
2967 [_contextMenuCoordinator addItemWithTitle:title action:action];
2968 }
olivierrobin51d4cf42017-01-17 13:32:352969 }
gambard65d69152017-03-23 17:44:222970 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352971 NSString* innerText = params.link_text;
2972 if ([innerText length] > 0) {
2973 // Add to reading list.
2974 title = l10n_util::GetNSStringWithFixup(
2975 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2976 action = ^{
2977 Record(ACTION_READ_LATER, isImage, isLink);
2978 [weakSelf addToReadingListURL:link title:innerText];
2979 };
2980 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532981 }
sdefresnee65fd872016-12-19 13:38:132982 }
2983 // Copy Link.
2984 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2985 action = ^{
2986 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282987 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132988 };
2989 [_contextMenuCoordinator addItemWithTitle:title action:action];
2990 }
2991 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192992 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132993 // Save Image.
gambard98b4ddf2017-04-18 07:14:052994 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132995 action = ^{
2996 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2997 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2998 };
2999 [_contextMenuCoordinator addItemWithTitle:title action:action];
3000 // Open Image.
3001 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3002 action = ^{
3003 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3004 [weakSelf loadURL:imageUrl
3005 referrer:referrer
3006 transition:ui::PAGE_TRANSITION_LINK
3007 rendererInitiated:YES];
3008 };
3009 [_contextMenuCoordinator addItemWithTitle:title action:action];
3010 // Open Image In New Tab.
3011 title = l10n_util::GetNSStringWithFixup(
3012 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3013 action = ^{
3014 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3015 [weakSelf webPageOrderedOpen:imageUrl
3016 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133017 inBackground:true
3018 appendTo:kCurrentTab];
3019 };
3020 [_contextMenuCoordinator addItemWithTitle:title action:action];
3021
3022 TemplateURLService* service =
3023 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103024 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133025 if (defaultURL && !defaultURL->image_url().empty() &&
3026 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3027 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3028 defaultURL->short_name());
3029 action = ^{
3030 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3031 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3032 };
3033 [_contextMenuCoordinator addItemWithTitle:title action:action];
3034 }
3035 }
3036
3037 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133038}
3039
eugenebutb739bdc2017-01-25 06:32:483040- (void)webState:(web::WebState*)webState
3041 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3042 // Display the action sheet with the arrow pointing at the top center of the
3043 // web contents.
sdefresne0452a9d2017-02-09 15:33:283044 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483045 UIView* view = webState->GetView();
3046 CGPoint dialogLocation =
3047 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283048 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033049 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173050 helper->PresentDialog(dialogLocation,
3051 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053052 handler(shouldContinue);
3053 }));
eugenebutb739bdc2017-01-25 06:32:483054}
3055
sdefresnee65fd872016-12-19 13:38:133056- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3057 (web::WebState*)webState {
3058 return _javaScriptDialogPresenter.get();
3059}
3060
eugenebut63232102017-01-19 16:19:403061- (void)webState:(web::WebState*)webState
3062 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3063 proposedCredential:(NSURLCredential*)proposedCredential
3064 completionHandler:(void (^)(NSString* username,
3065 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:423066 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3067 if ([tab isPrerenderTab]) {
3068 [tab discardPrerender];
3069 if (handler) {
3070 handler(nil, nil);
3071 }
3072 return;
3073 }
3074
eugenebut63232102017-01-19 16:19:403075 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3076 proposedCredential:proposedCredential
3077 webState:webState
3078 completionHandler:handler];
3079}
3080
sdefresnee65fd872016-12-19 13:38:133081#pragma mark - FullScreenControllerDelegate methods
3082
3083- (CGFloat)headerOffset {
3084 if (IsIPadIdiom())
3085 return StatusBarHeight();
3086 return 0.0;
3087}
3088
stkhapugin952ecef2017-04-11 12:11:453089- (NSArray<HeaderDefinition*>*)headerViews {
3090 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133091 if (![self isViewLoaded])
3092 return results;
3093
3094 if (!IsIPadIdiom()) {
sczsf1620e52017-10-02 22:54:463095 if ([_toolbarCoordinator view]) {
stkhapugin952ecef2017-04-11 12:11:453096 [results addObject:[HeaderDefinition
sczsf1620e52017-10-02 22:54:463097 definitionWithView:[_toolbarCoordinator view]
stkhapugin952ecef2017-04-11 12:11:453098 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243099 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453100 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133101 }
3102 } else {
edchinf5150c682017-09-18 02:50:033103 if (self.tabStripView) {
3104 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3105 headerBehaviour:Hideable
3106 heightAdjustment:0.0
3107 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133108 }
sczsf1620e52017-10-02 22:54:463109 if ([_toolbarCoordinator view]) {
stkhapugin952ecef2017-04-11 12:11:453110 [results addObject:[HeaderDefinition
sczsf1620e52017-10-02 22:54:463111 definitionWithView:[_toolbarCoordinator view]
stkhapugin952ecef2017-04-11 12:11:453112 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243113 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453114 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133115 }
3116 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453117 [results addObject:[HeaderDefinition
3118 definitionWithView:[_findBarController view]
3119 headerBehaviour:Overlap
3120 heightAdjustment:0.0
3121 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133122 }
3123 }
stkhapugin952ecef2017-04-11 12:11:453124 return [results copy];
sdefresnee65fd872016-12-19 13:38:133125}
3126
3127- (UIView*)footerView {
3128 return _voiceSearchBar;
3129}
3130
3131- (CGFloat)headerHeight {
3132 return [self headerHeightForTab:[_model currentTab]];
3133}
3134
3135- (CGFloat)headerHeightForTab:(Tab*)tab {
3136 id nativeController = [self nativeControllerForTab:tab];
3137 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3138 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3139 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3140 // On iPhone, don't add any header height for ToolbarOwner native
3141 // controllers when they're displaying their own toolbar.
3142 return 0;
3143 }
3144
stkhapugin952ecef2017-04-11 12:11:453145 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133146
3147 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453148 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133149 if (header.view && header.behaviour == Hideable) {
3150 height += CGRectGetHeight([header.view frame]) -
3151 header.heightAdjustement - header.inset;
3152 }
3153 }
3154
3155 return height - StatusBarHeight();
3156}
3157
3158- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193159 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133160}
3161
3162- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453163 NSArray<HeaderDefinition*>* headers = [self headerViews];
3164 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133165 return 0.0;
3166
3167 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3168 // API documentation.
3169 if ([[[self tabModel] currentTab] isPrerenderTab])
3170 return [self headerHeight];
3171
3172 UIView* topHeader = headers[0].view;
3173 return -(topHeader.frame.origin.y - [self headerOffset]);
3174}
3175
3176- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3177 UIView* footer = [self footerView];
3178 CGFloat headerHeight = [self headerHeight];
3179 if (!footer || headerHeight == 0)
3180 return 0.0;
3181
3182 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3183 CGFloat offset = headerOffset * footerHeight / headerHeight;
3184 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3185}
3186
3187- (void)fullScreenController:(FullScreenController*)controller
3188 headerAnimationCompleted:(BOOL)completed
3189 offset:(CGFloat)offset {
3190 if (completed)
justincohen04c27772016-12-21 20:16:593191 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133192}
3193
stkhapugin952ecef2017-04-11 12:11:453194- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133195 atOffset:(CGFloat)headerOffset {
3196 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453197 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133198 CGRect frame = [header.view frame];
3199 frame.origin.y = height - headerOffset - header.inset;
3200 [header.view setFrame:frame];
3201 if (header.behaviour != Overlap)
3202 height += CGRectGetHeight(frame);
3203 }
3204}
3205
3206- (void)fullScreenController:(FullScreenController*)fullScreenController
3207 drawHeaderViewFromOffset:(CGFloat)headerOffset
3208 animate:(BOOL)animate {
3209 if ([_sideSwipeController inSwipe])
3210 return;
3211
3212 CGRect footerFrame = CGRectZero;
3213 UIView* footer = nil;
3214 // Only animate the voice search bar if the tab is a voice search results tab.
3215 if ([_model currentTab].isVoiceSearchResultsTab) {
3216 footer = [self footerView];
3217 footerFrame = footer.frame;
3218 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3219 }
3220
stkhapugin952ecef2017-04-11 12:11:453221 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133222 void (^block)(void) = ^{
3223 [self setFramesForHeaders:headers atOffset:headerOffset];
3224 footer.frame = footerFrame;
3225 };
3226 void (^completion)(BOOL) = ^(BOOL finished) {
3227 [self fullScreenController:fullScreenController
3228 headerAnimationCompleted:finished
3229 offset:headerOffset];
3230 };
3231 if (animate) {
Sylvain Defresneed8c0db2017-08-31 16:29:523232 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133233 delay:0.0
3234 options:UIViewAnimationOptionBeginFromCurrentState
3235 animations:block
3236 completion:completion];
3237 } else {
3238 block();
3239 completion(YES);
3240 }
3241}
3242
3243- (void)fullScreenController:(FullScreenController*)fullScreenController
3244 drawHeaderViewFromOffset:(CGFloat)headerOffset
3245 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3246 changeTopContentPadding:(BOOL)changeTopContentPadding
3247 scrollingToOffset:(CGFloat)contentOffset {
3248 DCHECK(webViewProxy);
3249 if ([_sideSwipeController inSwipe])
3250 return;
3251
3252 CGRect footerFrame;
3253 UIView* footer = nil;
3254 // Only animate the voice search bar if the tab is a voice search results tab.
3255 if ([_model currentTab].isVoiceSearchResultsTab) {
3256 footer = [self footerView];
3257 footerFrame = footer.frame;
3258 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3259 }
3260
stkhapugin952ecef2017-04-11 12:11:453261 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133262 void (^block)(void) = ^{
3263 [self setFramesForHeaders:headers atOffset:headerOffset];
3264 footer.frame = footerFrame;
3265 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3266 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3267 if (changeTopContentPadding)
3268 webViewProxy.topContentPadding = contentOffset;
3269 };
3270 void (^completion)(BOOL) = ^(BOOL finished) {
3271 [self fullScreenController:fullScreenController
3272 headerAnimationCompleted:finished
3273 offset:headerOffset];
3274 };
3275
Sylvain Defresneed8c0db2017-08-31 16:29:523276 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133277 delay:0.0
3278 options:UIViewAnimationOptionBeginFromCurrentState
3279 animations:block
3280 completion:completion];
3281}
3282
3283#pragma mark - VoiceSearchBarOwner
3284
3285- (id<VoiceSearchBar>)voiceSearchBar {
3286 return _voiceSearchBar;
3287}
3288
3289#pragma mark - Install OverScrollActionController method.
3290- (void)setOverScrollActionControllerToStaticNativeContent:
3291 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343292 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133293 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173294 [[OverscrollActionsController alloc]
3295 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133296 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053297 OverscrollStyle style = _isOffTheRecord
3298 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3299 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133300 controller.style = style;
3301 nativeContent.overscrollActionsController = controller;
3302 }
3303}
3304
3305#pragma mark - OverscrollActionsControllerDelegate methods.
3306
3307- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053308 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133309 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053310 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383311 [self.dispatcher
3312 openNewTab:[OpenNewTabCommand
3313 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133314 break;
rohitrao922b7111c2017-01-03 14:31:053315 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243316 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133317 break;
liaoyuke563dc4a2017-03-17 18:36:293318 case OverscrollAction::REFRESH: {
liaoyuke563dc4a2017-03-17 18:36:293319 web::WebState* webState = [_model currentTab].webState;
Eugene But083b6c7a2017-10-02 15:49:383320 if (webState) {
3321 if (webState->IsLoading()) {
3322 webState->Stop();
3323 }
liaoyuke563dc4a2017-03-17 18:36:293324 // |check_for_repost| is true because the reload is explicitly initiated
3325 // by the user.
3326 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3327 true /* check_for_repost */);
Eugene But083b6c7a2017-10-02 15:49:383328 }
sdefresnee65fd872016-12-19 13:38:133329 break;
liaoyuke563dc4a2017-03-17 18:36:293330 }
rohitrao922b7111c2017-01-03 14:31:053331 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133332 NOTREACHED();
3333 break;
3334 }
3335}
3336
3337- (BOOL)shouldAllowOverscrollActions {
3338 return YES;
3339}
3340
3341- (UIView*)headerView {
sczsf1620e52017-10-02 22:54:463342 return [_toolbarCoordinator view];
sdefresnee65fd872016-12-19 13:38:133343}
3344
3345- (UIView*)toolbarSnapshotView {
sczsf1620e52017-10-02 22:54:463346 return [[_toolbarCoordinator view] snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133347}
3348
3349- (CGFloat)overscrollActionsControllerHeaderInset:
3350 (OverscrollActionsController*)controller {
3351 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3352 return [self headerHeight];
3353 else
3354 return 0;
3355}
3356
3357- (CGFloat)overscrollHeaderHeight {
3358 return [self headerHeight] + StatusBarHeight();
3359}
3360
3361#pragma mark - TabSnapshottingDelegate methods.
3362
3363- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3364 CGRect pageContentArea = _contentArea.bounds;
3365 if ([_model webUsageEnabled])
3366 pageContentArea = tab.view.bounds;
3367 CGFloat headerHeight = [self headerHeightForTab:tab];
3368 id nativeController = [self nativeControllerForTab:tab];
3369 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3370 headerHeight += [nativeController toolbarHeight];
3371 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3372 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3373}
3374
3375#pragma mark - NewTabPageObserver methods.
3376
3377- (void)selectedPanelDidChange {
3378 [self updateToolbar];
3379}
3380
3381#pragma mark - CRWNativeContentProvider methods
3382
3383- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3384 withError:(NSError*)error
3385 isPost:(BOOL)isPost {
3386 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173387 [[ErrorPageContent alloc] initWithLoader:self
3388 browserState:self.browserState
3389 url:url
3390 error:error
3391 isPost:isPost
3392 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133393 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3394 return errorPageContent;
3395}
3396
3397- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003398 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453399 if (host == kChromeUIOfflineHost) {
3400 // Only allow offline URL that are fully specified.
3401 return reading_list::IsOfflineURLValid(
3402 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3403 }
sdefresnee65fd872016-12-19 13:38:133404
Justin Cohen8679e852017-08-14 16:35:253405 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003406 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253407 }
3408
3409 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133410}
3411
olivierrobind43eecb2017-01-27 20:35:263412- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3413 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133414 DCHECK(url.SchemeIs(kChromeUIScheme));
3415
3416 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003417 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193418 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003419 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463420 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3421 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3422 if (@available(iOS 11.0, *)) {
3423 safeAreaInset = self.view.safeAreaInsets;
3424 }
3425 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3426
sdefresnee65fd872016-12-19 13:38:133427 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173428 [[NewTabPageController alloc] initWithUrl:url
3429 loader:self
sczsf1620e52017-10-02 22:54:463430 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173431 ntpObserver:self
3432 browserState:_browserState
3433 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463434 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453435 tabModel:_model
justincohen75011c32017-04-28 16:31:393436 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463437 dispatcher:self.dispatcher
3438 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133439 pageController.swipeRecognizerProvider = self.sideSwipeController;
3440
3441 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233442 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133443
Marti Wong64481ec2017-10-31 03:38:003444 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133445 // New Tab Page can have multiple panels. Each panel is addressable
3446 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3447 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3448 // the Bookmark Manager, etc.
3449 // The utility functions NewTabPage::IdentifierFromFragment() and
3450 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3451 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3452 // without changing the URL since the URL may be chrome://bookmarks/#123.
3453 // If the URL is chrome://newtab/, pre-select the panel based on the
3454 // #fragment.
3455 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233456 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133457 : NewTabPage::IdentifierFromFragment(url.ref());
3458 }
3459 [pageController selectPanel:panelType];
3460 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453461 } else if (url_host == kChromeUIOfflineHost &&
3462 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133463 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173464 [[OfflinePageNativeContent alloc] initWithLoader:self
3465 browserState:_browserState
3466 webState:webState
3467 URL:url];
sdefresnee65fd872016-12-19 13:38:133468 [self setOverScrollActionControllerToStaticNativeContent:
3469 staticNativeController];
3470 nativeController = staticNativeController;
3471 } else if (url_host == kChromeUIExternalFileHost) {
3472 // Return an instance of the |ExternalFileController| only if the file is
3473 // still in the sandbox.
3474 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3475 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173476 nativeController =
3477 [[ExternalFileController alloc] initWithURL:url
3478 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133479 }
peterlaurens44615d02017-05-23 20:23:093480 } else if (url_host == kChromeUICrashHost) {
3481 // There is no native controller for kChromeUICrashHost, it is instead
3482 // handled as any other renderer crash by the SadTabTabHelper.
3483 // nativeController must be set to nil to prevent defaulting to a
3484 // PageNotAvailableController.
3485 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133486 } else {
3487 DCHECK(![self hasControllerForURL:url]);
3488 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173489 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133490 }
3491 // If a native controller is vended before its tab is added to the tab model,
3492 // use the temporary key and add it under the new tab's tabId in the
3493 // TabModelObserver callback. This happens:
3494 // - when there is no current tab (occurs when vending the NTP controller for
3495 // the first tab that is opened),
3496 // - when the current tab's url doesn't match |url| (occurs when a native
3497 // controller is opened in a new tab)
3498 // - when the current tab's url matches |url| and there is already a native
3499 // controller of the appropriate type vended to it (occurs when a native
3500 // controller is opened in a new tab from a tab with a matching URL, e.g.
3501 // opening an NTP when an NTP is already displayed in the current tab).
3502 // For normal page loads, history navigations, tab restorations, and crash
3503 // recoveries, the tab will already exist in the tab model and the tabId can
3504 // be used as the native controller key.
3505 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3506 // that native controllers vended here always correspond to the current tab.
3507 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193508 if (!currentTab.webState ||
3509 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443510 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133511 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443512 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133513 }
sdefresnee65fd872016-12-19 13:38:133514 return nativeController;
3515}
3516
3517- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443518 id nativeController = tab.webController.nativeController;
3519 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133520}
3521
3522#pragma mark - DialogPresenterDelegate methods
3523
3524- (void)dialogPresenter:(DialogPresenter*)presenter
3525 willShowDialogForWebState:(web::WebState*)webState {
3526 for (Tab* iteratedTab in self.tabModel) {
3527 if ([iteratedTab webState] == webState) {
3528 self.tabModel.currentTab = iteratedTab;
3529 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3530 break;
3531 }
3532 }
3533}
3534
3535#pragma mark - Context menu methods
3536
3537- (void)searchByImageAtURL:(const GURL&)url
3538 referrer:(const web::Referrer)referrer {
3539 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273540 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113541 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173542 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3543 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113544 DCHECK(data);
3545 dispatch_async(dispatch_get_main_queue(), ^{
3546 [weakSelf searchByImageData:data atURL:image_source_url];
3547 });
3548 };
3549 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133550 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3551 web::PolicyForNavigation(url, referrer));
3552}
3553
3554- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3555 NSData* imageData = data;
3556 UIImage* image = [UIImage imageWithData:imageData];
3557 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3558 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3559 // kSearchByImageMaxImageHeight).
3560 if (image &&
3561 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3562 (image.size.width > kSearchByImageMaxImageWidth ||
3563 image.size.height > kSearchByImageMaxImageHeight)) {
3564 CGSize newImageSize =
3565 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3566 image = [image gtm_imageByResizingToSize:newImageSize
3567 preserveAspectRatio:YES
3568 trimToFit:NO];
3569 imageData = UIImageJPEGRepresentation(image, 1.0);
3570 }
3571
3572 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3573 std::string byteString(bytes, [imageData length]);
3574
3575 TemplateURLService* templateUrlService =
3576 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103577 const TemplateURL* defaultURL =
3578 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133579 DCHECK(!defaultURL->image_url().empty());
3580 DCHECK(defaultURL->image_url_ref().IsValid(
3581 templateUrlService->search_terms_data()));
3582 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3583 search_args.image_url = imageURL;
3584 search_args.image_thumbnail_content = byteString;
3585
3586 // Generate the URL and populate |post_content| with the content type and
3587 // HTTP body for the request.
3588 TemplateURLRef::PostContent post_content;
3589 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3590 search_args, templateUrlService->search_terms_data(), &post_content));
3591 [self addSelectedTabWithURL:result
3592 postData:&post_content
3593 transition:ui::PAGE_TRANSITION_TYPED];
3594}
3595
3596- (void)saveImageAtURL:(const GURL&)url
3597 referrer:(const web::Referrer&)referrer {
3598 DCHECK(url.is_valid());
3599
gambard9efce7a2017-02-09 18:53:173600 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3601 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113602 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133603
gambardbbf85c42017-06-29 11:15:343604 if ([data length] == 0) {
3605 [self displayPrivacyErrorAlertOnMainQueue:
3606 l10n_util::GetNSString(
3607 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3608 return;
3609 }
3610
gambard9efce7a2017-02-09 18:53:173611 base::FilePath::StringType extension;
3612
3613 bool extensionSuccess =
3614 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3615 if (!extensionSuccess || extension.length() == 0) {
3616 extension = "png";
3617 }
3618
3619 NSString* fileExtension =
3620 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3621 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113622 };
3623 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133624 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3625 web::PolicyForNavigation(url, referrer));
3626}
3627
gambard9efce7a2017-02-09 18:53:173628- (void)managePermissionAndSaveImage:(NSData*)data
3629 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133630 switch ([PHPhotoLibrary authorizationStatus]) {
3631 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173632 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133633 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3634 // Call -saveImage again to check if chrome needs to display an error or
3635 // saves the image.
3636 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173637 [self managePermissionAndSaveImage:data
3638 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133639 }];
3640 break;
stkhapuginf58b10d02017-04-10 13:36:173641 }
sdefresnee65fd872016-12-19 13:38:133642
3643 // The application doesn't have permission to access photo and the user
3644 // cannot grant it.
3645 case PHAuthorizationStatusRestricted:
3646 [self displayPrivacyErrorAlertOnMainQueue:
3647 l10n_util::GetNSString(
3648 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3649 break;
3650
3651 // The application doesn't have permission to access photo and the user
3652 // can grant it.
3653 case PHAuthorizationStatusDenied:
3654 [self displayImageErrorAlertWithSettingsOnMainQueue];
3655 break;
3656
3657 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243658 default:
3659 __weak BrowserViewController* weakSelf = self;
3660 [self saveImage:data
3661 withFileExtension:fileExtension
3662 completion:^(BOOL success, NSError* error) {
3663 [weakSelf finishSavingImageWithError:error];
3664 }];
sdefresnee65fd872016-12-19 13:38:133665 break;
sdefresnee65fd872016-12-19 13:38:133666 }
3667}
3668
Sylvain Defresnefd3ecf22017-07-12 18:47:243669- (void)saveImage:(NSData*)data
3670 withFileExtension:(NSString*)fileExtension
3671 completion:(void (^)(BOOL, NSError*))completion {
3672 base::PostTaskWithTraits(
3673 FROM_HERE,
3674 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3675 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3676 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373677 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133678
Sylvain Defresnefd3ecf22017-07-12 18:47:243679 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3680 stringByAppendingString:fileExtension];
3681 NSURL* fileURL = [NSURL
3682 fileURLWithPath:[NSTemporaryDirectory()
3683 stringByAppendingPathComponent:fileName]];
3684 NSError* error = nil;
3685 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3686 if (error) {
3687 if (completion)
3688 completion(NO, error);
3689 return;
3690 }
sdefresnee65fd872016-12-19 13:38:133691
Sylvain Defresnefd3ecf22017-07-12 18:47:243692 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3693 [PHAssetChangeRequest
3694 creationRequestForAssetFromImageAtFileURL:fileURL];
3695 }
3696 completionHandler:^(BOOL success, NSError* error) {
3697 base::PostTaskWithTraits(
3698 FROM_HERE,
3699 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3700 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3701 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373702 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243703 if (completion)
3704 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133705
Sylvain Defresnefd3ecf22017-07-12 18:47:243706 // Cleanup the temporary file.
3707 NSError* deleteFileError = nil;
3708 [[NSFileManager defaultManager]
3709 removeItemAtURL:fileURL
3710 error:&deleteFileError];
3711 }));
3712 }];
3713 }));
sdefresnee65fd872016-12-19 13:38:133714}
3715
3716- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3717 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3718 BOOL canGoToSetting =
3719 [[UIApplication sharedApplication] canOpenURL:settingURL];
3720 if (canGoToSetting) {
3721 dispatch_async(dispatch_get_main_queue(), ^{
3722 [self displayImageErrorAlertWithSettings:settingURL];
3723 });
3724 } else {
3725 [self displayPrivacyErrorAlertOnMainQueue:
3726 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3727 }
3728}
3729
3730- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3731 // Dismiss current alert.
3732 [_alertCoordinator stop];
3733
3734 NSString* title =
3735 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3736 NSString* message = l10n_util::GetNSString(
3737 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3738
stkhapuginc9eee7b2017-04-10 15:49:273739 _alertCoordinator =
3740 [[AlertCoordinator alloc] initWithBaseViewController:self
3741 title:title
3742 message:message];
sdefresnee65fd872016-12-19 13:38:133743
3744 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3745 action:nil
3746 style:UIAlertActionStyleCancel];
3747
3748 [_alertCoordinator
3749 addItemWithTitle:l10n_util::GetNSString(
3750 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3751 action:^{
3752 OpenUrlWithCompletionHandler(settingURL, nil);
3753 }
3754 style:UIAlertActionStyleDefault];
3755
3756 [_alertCoordinator start];
3757}
3758
3759- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3760 dispatch_async(dispatch_get_main_queue(), ^{
3761 NSString* title =
3762 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3763 [self showErrorAlertWithStringTitle:title message:errorContent];
3764 });
3765}
3766
3767// This callback is triggered when the image is effectively saved onto the photo
3768// album, or if the save failed for some reason.
3769- (void)finishSavingImageWithError:(NSError*)error {
3770 // Was there an error?
3771 if (error) {
3772 // Saving photo failed even though user has granted access to Photos.
3773 // Display the error information from the NSError object for user.
3774 NSString* errorMessage = [NSString
3775 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3776 [error domain], [error code]];
3777 // This code may be execute outside of the main thread. Make sure to display
3778 // the error on the main thread.
3779 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3780 } else {
3781 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3782 // the photo application. The current behaviour is to create the photo there
3783 // but not providing any link to it is suboptimal. That's what Safari is
3784 // doing, and what the PM want, but it doesn't make it right.
3785 }
3786}
3787
sdefresnee65fd872016-12-19 13:38:133788#pragma mark - Showing popups
3789
sdefresnee65fd872016-12-19 13:38:133790- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133791 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3792
3793 ReadingListModel* readingModel =
3794 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013795 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3796 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133797
pinkerton07e27842017-03-02 15:29:023798 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363799 [self showSnackbar:l10n_util::GetNSString(
3800 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133801}
3802
3803#pragma mark - Keyboard commands management
3804
3805- (BOOL)shouldRegisterKeyboardCommands {
3806 if ([self presentedViewController])
3807 return NO;
3808
3809 if (_voiceSearchController && _voiceSearchController->IsVisible())
3810 return NO;
3811
3812 // If there is no first responder, try to make the webview the first
3813 // responder.
3814 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303815 web::WebState* webState = _model.currentTab.webState;
3816 if (webState)
3817 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133818 }
3819
3820 return YES;
3821}
3822
3823- (KeyCommandsProvider*)keyCommandsProvider {
3824 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273825 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133826 }
stkhapuginc9eee7b2017-04-10 15:49:273827 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133828}
3829
3830#pragma mark - KeyCommandsPlumbing
3831
3832- (BOOL)isOffTheRecord {
3833 return _isOffTheRecord;
3834}
3835
3836- (NSUInteger)tabsCount {
3837 return [_model count];
3838}
3839
lpromero47ea8862017-01-13 17:51:063840- (BOOL)canGoBack {
3841 return [_model currentTab].canGoBack;
3842}
3843
3844- (BOOL)canGoForward {
3845 return [_model currentTab].canGoForward;
3846}
3847
sdefresnee65fd872016-12-19 13:38:133848- (void)focusTabAtIndex:(NSUInteger)index {
3849 if ([_model count] > index) {
3850 [_model setCurrentTab:[_model tabAtIndex:index]];
3851 }
3852}
3853
3854- (void)focusNextTab {
3855 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3856 NSInteger modelCount = [_model count];
3857 if (currentTabIndex < modelCount - 1) {
3858 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3859 [_model setCurrentTab:nextTab];
3860 } else {
3861 [_model setCurrentTab:[_model tabAtIndex:0]];
3862 }
3863}
3864
3865- (void)focusPreviousTab {
3866 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3867 if (currentTabIndex > 0) {
3868 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3869 [_model setCurrentTab:previousTab];
3870 } else {
3871 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3872 [_model setCurrentTab:lastTab];
3873 }
3874}
3875
3876- (void)reopenClosedTab {
3877 sessions::TabRestoreService* const tabRestoreService =
3878 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3879 if (!tabRestoreService || tabRestoreService->entries().empty())
3880 return;
3881
3882 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3883 tabRestoreService->entries().front();
3884 // Only handle the TAB type.
3885 if (entry->type != sessions::TabRestoreService::TAB)
3886 return;
3887
Mark Cogandfcdea72017-07-18 13:47:383888 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133889 TabRestoreServiceDelegateImplIOS* const delegate =
3890 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3891 _browserState);
3892 tabRestoreService->RestoreEntryById(delegate, entry->id,
3893 WindowOpenDisposition::CURRENT_TAB);
3894}
3895
3896- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463897 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133898}
3899
3900#pragma mark - UIResponder
3901
3902- (NSArray*)keyCommands {
3903 if (![self shouldRegisterKeyboardCommands]) {
3904 return nil;
3905 }
3906 return [self.keyCommandsProvider
3907 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543908 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243909 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133910 editingText:![self isFirstResponder]];
3911}
3912
3913#pragma mark -
3914
3915// Induce an intentional crash in the browser process.
3916- (void)induceBrowserCrash {
3917 CHECK(false);
3918 // Call another function, so that the above CHECK can't be tail-call
3919 // optimized. This ensures that this method's name will show up in the stack
3920 // for easier identification.
3921 CHECK(true);
3922}
3923
3924- (void)loadURL:(const GURL&)url
3925 referrer:(const web::Referrer&)referrer
3926 transition:(ui::PageTransition)transition
3927 rendererInitiated:(BOOL)rendererInitiated {
3928 [[OmniboxGeolocationController sharedInstance]
3929 locationBarDidSubmitURL:url
3930 transition:transition
3931 browserState:_browserState];
3932
3933 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3934 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3935 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3936 }
3937
3938 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243939 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133940 // restart.
3941 if (url.host() == kChromeUIBrowserCrashHost) {
3942 [self induceBrowserCrash];
3943 // In debug the app can continue working even after the CHECK. Adding a
3944 // return avoids the crash url to be added to the history.
3945 return;
3946 }
3947
Danyao Wang85389a82017-10-25 18:56:273948 bool typed_or_generated_transition =
3949 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3950 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3951
Rohit Rao44f204302017-08-10 14:49:543952 PrerenderService* prerenderService =
3953 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3954 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593955 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543956 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593957 DCHECK(newWebState);
3958
sdefresnee65fd872016-12-19 13:38:133959 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593960 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133961 DCHECK(oldTab);
3962 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593963
kkhorimotod804c5732017-03-15 23:44:523964 bool canPruneItems =
3965 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593966
kkhorimotod804c5732017-03-15 23:44:523967 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523968 [newTab navigationManager]->CopyStateFromAndPrune(
3969 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593970
3971 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3972 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133973
3974 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3975 // BrowserViewController to detect that a pre-rendered tab is switched in,
3976 // and show the prerendering animation.
3977 newTab.isPrerenderTab = NO;
Danyao Wang85389a82017-10-25 18:56:273978 if (typed_or_generated_transition) {
3979 LoadTimingTabHelper::FromWebState(newTab.webState)
3980 ->DidPromotePrerenderTab();
3981 }
sdefresnee65fd872016-12-19 13:38:133982
sdefresne2f7781c2017-03-02 19:12:463983 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133984 return;
3985 }
3986 }
3987
3988 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543989 if (prerenderService) {
3990 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133991 }
3992
sdefresnee65fd872016-12-19 13:38:133993 // Some URLs are not allowed while in incognito. If we are in incognito and
3994 // load a disallowed URL, instead create a new tab not in the incognito state.
3995 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3996 [self webPageOrderedOpen:url
3997 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133998 inIncognito:NO
3999 inBackground:NO
4000 appendTo:kCurrentTab];
4001 return;
4002 }
4003
Danyao Wang85389a82017-10-25 18:56:274004 if (typed_or_generated_transition) {
4005 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4006 ->DidInitiatePageLoad();
4007 }
4008
mrefaata84d5a02017-06-08 17:13:294009 // If this is a reload initiated from the omnibox.
4010 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4011 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4012 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4013 [[_model currentTab] navigationManager]->Reload(
4014 web::ReloadType::NORMAL, true /* check_for_repost */);
4015 return;
4016 }
4017
sdefresnee65fd872016-12-19 13:38:134018 web::NavigationManager::WebLoadParams params(urlToLoad);
4019 params.referrer = referrer;
4020 params.transition_type = transition;
4021 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104022 Tab* currentTab = [_model currentTab];
4023 DCHECK(currentTab);
4024 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4025 currentTab.navigationManager->LoadURLWithParams(params);
4026 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4027 // to be animated on screen because the transition animator will handle the
4028 // animations. When a Tab stops being a voice search Tab, the voice search
4029 // bar should be animated away.
4030 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4031 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134032}
4033
4034- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544035 PrerenderService* prerenderService =
4036 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4037 if (prerenderService) {
4038 prerenderService->CancelPrerender();
4039 }
sdefresnee65fd872016-12-19 13:38:134040 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184041 if ([self currentWebState])
4042 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134043}
4044
4045- (web::WebState*)currentWebState {
4046 return [[_model currentTab] webState];
4047}
4048
sdefresnee65fd872016-12-19 13:38:134049// Load a new URL on a new page/tab.
4050- (void)webPageOrderedOpen:(const GURL&)URL
4051 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134052 inBackground:(BOOL)inBackground
4053 appendTo:(OpenPosition)appendTo {
4054 Tab* adjacentTab = nil;
4055 if (appendTo == kCurrentTab)
4056 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354057 [_model insertTabWithURL:URL
4058 referrer:referrer
4059 transition:ui::PAGE_TRANSITION_LINK
4060 opener:adjacentTab
4061 openedByDOM:NO
4062 atIndex:TabModelConstants::kTabPositionAutomatically
4063 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134064}
4065
4066- (void)webPageOrderedOpen:(const GURL&)url
4067 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134068 inIncognito:(BOOL)inIncognito
4069 inBackground:(BOOL)inBackground
4070 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004071 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234072 // feature_engagement::Tracker based on |inIncognito|.
4073 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004074
sdefresnee65fd872016-12-19 13:38:134075 if (inIncognito == _isOffTheRecord) {
4076 [self webPageOrderedOpen:url
4077 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134078 inBackground:inBackground
4079 appendTo:appendTo];
4080 return;
4081 }
4082 // When sending an open command that switches modes, ensure the tab
4083 // ends up appended to the end of the model, not just next to what is
4084 // currently selected in the other mode. This is done with the |append|
4085 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274086 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134087 initWithURL:url
4088 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134089 inIncognito:inIncognito
4090 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274091 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154092 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134093}
4094
4095- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054096 WebStateList* webStateList = [_model webStateList];
4097 webStateList->ReplaceWebStateAt(
4098 webStateList->active_index(),
4099 session_util::CreateWebStateWithNavigationEntries(
4100 [_model browserState], sessionTab->current_navigation_index,
4101 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134102}
4103
4104- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434105 DCHECK(javascript);
4106 javascript = [javascript stringByRemovingPercentEncoding];
4107 web::WebState* webState = [[_model currentTab] webState];
4108 if (webState) {
4109 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4110 }
sdefresnee65fd872016-12-19 13:38:134111}
4112
4113#pragma mark - WebToolbarDelegate methods
4114
4115- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
4116 if (_locationBarHasFocus)
4117 return; // TODO(crbug.com/244366): This should not be necessary.
4118 _locationBarHasFocus = YES;
4119 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524120 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134121 object:nil];
4122 [_sideSwipeController setEnabled:NO];
4123 if ([[_model currentTab].webController wantsKeyboardShield]) {
4124 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4125 [_typingShield setAlpha:0.0];
4126 [_typingShield setHidden:NO];
4127 [UIView animateWithDuration:0.3
4128 animations:^{
4129 [_typingShield setAlpha:1.0];
4130 }];
4131 }
4132 [[OmniboxGeolocationController sharedInstance]
4133 locationBarDidBecomeFirstResponder:_browserState];
4134}
4135
4136- (IBAction)locationBarDidResignFirstResponder:(id)sender {
4137 if (!_locationBarHasFocus)
4138 return; // TODO(crbug.com/244366): This should not be necessary.
4139 _locationBarHasFocus = NO;
4140 [_sideSwipeController setEnabled:YES];
4141 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524142 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134143 object:nil];
4144 [UIView animateWithDuration:0.3
4145 animations:^{
4146 [_typingShield setAlpha:0.0];
4147 }
4148 completion:^(BOOL finished) {
4149 // This can happen if one quickly resigns the omnibox and then taps
4150 // on the omnibox again during this animation. If the animation is
4151 // interrupted and the toolbar controller is first responder, it's safe
4152 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464153 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134154 return;
4155 [_typingShield setHidden:YES];
4156 }];
4157 [[OmniboxGeolocationController sharedInstance]
4158 locationBarDidResignFirstResponder:_browserState];
4159
4160 // If a load was cancelled by an omnibox edit, but nothing is loading when
4161 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4162 if (_locationBarEditCancelledLoad) {
4163 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294164
4165 web::WebState* webState = [_model currentTab].webState;
4166 if (!_toolbarModelIOS->IsLoading() && webState)
4167 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4168 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134169 }
4170}
4171
4172- (IBAction)locationBarBeganEdit:(id)sender {
4173 // On handsets, if a page is currently loading it should be stopped.
4174 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354175 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134176 _locationBarEditCancelledLoad = YES;
4177 }
4178}
4179
sdefresnee65fd872016-12-19 13:38:134180- (ToolbarModelIOS*)toolbarModelIOS {
4181 return _toolbarModelIOS.get();
4182}
4183
sdefresnee65fd872016-12-19 13:38:134184- (void)willUpdateToolbarSnapshot {
4185 [[_model currentTab].overscrollActionsController clear];
4186}
4187
4188- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4189 CGRect frame = [_contentArea frame];
4190 if (!fullScreen) {
4191 // Changing the origin here is unnecessary, it's set in page_animation_util.
4192 frame.size.height -= [self headerHeight];
4193 }
4194
4195 CGFloat shortAxis = frame.size.width;
4196 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524197 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134198 CGFloat aspectRatio = frame.size.height / frame.size.width;
4199 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4200 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4201 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4202 CGRect cardFrame = {frame.origin, cardSize};
4203
4204 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174205 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134206 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4207 : CardCloseButtonSide::LEADING;
4208 [_contentArea addSubview:card];
4209 return card;
4210}
4211
Mark Cogan6ebbde02017-07-07 12:50:134212#pragma mark - BrowserCommands
4213
4214- (void)goBack {
4215 [[_model currentTab] goBack];
4216}
4217
4218- (void)goForward {
4219 [[_model currentTab] goForward];
4220}
4221
Mark Coganb9aac6432017-07-07 13:26:354222- (void)stopLoading {
4223 [_model currentTab].webState->Stop();
4224}
4225
4226- (void)reload {
4227 web::WebState* webState = [_model currentTab].webState;
4228 if (webState) {
4229 // |check_for_repost| is true because the reload is explicitly initiated
4230 // by the user.
4231 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4232 true /* check_for_repost */);
4233 }
4234}
4235
Mark Cogan8e791022017-07-10 09:55:354236- (void)bookmarkPage {
4237 [self initializeBookmarkInteractionController];
4238 [_bookmarkInteractionController
4239 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064240 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354241}
4242
Mark Cogan6acee7f2017-07-11 09:01:404243- (void)showToolsMenu {
4244 DCHECK(_browserState);
4245 DCHECK(self.visible || self.dismissingModal);
4246
4247 // Record the time this menu was requested; to be stored in the configuration
4248 // object.
4249 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4250
4251 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:464252 [_toolbarCoordinator cancelOmniboxEdit];
Mark Cogan6acee7f2017-07-11 09:01:404253 // Dismiss the soft keyboard (if open).
4254 [[_model currentTab].webController dismissKeyboard];
4255 // Dismiss Find in Page focus.
4256 [self updateFindBar:NO shouldFocus:NO];
4257
4258 ToolsMenuConfiguration* configuration =
edchin8e4cfe032017-10-25 13:25:544259 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4260 baseViewController:self];
Mark Cogan6acee7f2017-07-11 09:01:404261 configuration.requestStartTime =
4262 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4263 if ([_model count] == 0)
4264 [configuration setNoOpenedTabs:YES];
4265
4266 if (_isOffTheRecord)
4267 [configuration setInIncognito:YES];
4268
4269 if (!_readingListMenuNotifier) {
4270 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4271 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4272 _browserState)];
4273 }
Cooper Knaake4f495cf2017-07-27 23:30:034274
4275 feature_engagement::Tracker* engagementTracker =
4276 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4277 if (engagementTracker->ShouldTriggerHelpUI(
4278 feature_engagement::kIPHBadgedReadingListFeature)) {
4279 [configuration setShowReadingListNewBadge:YES];
4280 [configuration setEngagementTracker:engagementTracker];
4281 }
Mark Cogan6acee7f2017-07-11 09:01:404282 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4283
4284 [configuration setUserAgentType:self.userAgentType];
4285
Helen Yang9175bd52017-08-12 00:28:404286 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4287 [configuration setHighlightNewIncognitoTabCell:YES];
4288 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4289 }
4290
4291 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4292 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4293 }
4294
sczsf1620e52017-10-02 22:54:464295 [_toolbarCoordinator showToolsMenuPopupWithConfiguration:configuration];
Mark Cogan6acee7f2017-07-11 09:01:404296
4297 ToolsPopupController* toolsPopupController =
sczsf1620e52017-10-02 22:54:464298 [_toolbarCoordinator toolsPopupController];
Mark Cogan6acee7f2017-07-11 09:01:404299 if ([_model currentTab]) {
4300 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4301 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4302 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404303 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4304
4305 if (!IsIPadIdiom())
4306 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4307 }
4308}
4309
Mark Cogandfcdea72017-07-18 13:47:384310- (void)openNewTab:(OpenNewTabCommand*)command {
4311 if (self.isOffTheRecord != command.incognito) {
4312 // Not for this browser state, send it on its way.
4313 [self.dispatcher switchModesAndOpenNewTab:command];
4314 return;
4315 }
4316
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004317 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234318 // events to the feature_engagement::Tracker based on |command.userInitiated|
4319 // and |command.incognito|.
4320 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004321
Mark Cogandfcdea72017-07-18 13:47:384322 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4323 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494324 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4325 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384326 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494327 if (oldForegroundTabWasAddedCompletionBlock) {
4328 oldForegroundTabWasAddedCompletionBlock();
4329 }
Mark Cogandfcdea72017-07-18 13:47:384330 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4331 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4332 if (offTheRecord) {
4333 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4334 timeDelta);
4335 } else {
4336 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4337 }
4338 };
4339
4340 [self setLastTapPoint:command];
4341 DCHECK(self.visible || self.dismissingModal);
4342 Tab* currentTab = [_model currentTab];
4343 if (currentTab) {
4344 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4345 }
4346 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4347 transition:ui::PAGE_TRANSITION_TYPED];
4348}
4349
Mark Cogan123895002017-07-20 12:54:064350- (void)printTab {
4351 Tab* currentTab = [_model currentTab];
4352 // The UI should prevent users from printing non-printable pages. However, a
4353 // redirection to an un-printable page can happen before it is reflected in
4354 // the UI.
4355 if (![currentTab viewForPrinting]) {
4356 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364357 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064358 return;
4359 }
4360 DCHECK(_browserState);
4361 if (!_printController) {
4362 _printController = [[PrintController alloc]
4363 initWithContextGetter:_browserState->GetRequestContext()];
4364 }
4365 [_printController printView:[currentTab viewForPrinting]
4366 withTitle:[currentTab title]
4367 viewController:self];
4368}
4369
Mark Coganfa25b052017-07-20 17:31:034370- (void)addToReadingList:(ReadingListAddCommand*)command {
4371 [self addToReadingListURL:[command URL] title:[command title]];
4372}
4373
sczs3a8c8602017-08-01 20:14:084374- (void)showReadingList {
4375 _readingListCoordinator = [[ReadingListCoordinator alloc]
4376 initWithBaseViewController:self
4377 browserState:self.browserState
4378 loader:self];
4379
4380 [_readingListCoordinator start];
4381}
4382
Jean-François Geyelinedef9552017-08-07 09:56:564383- (void)preloadVoiceSearch {
4384 // Preload VoiceSearchController and views and view controllers needed
4385 // for voice search.
4386 [self ensureVoiceSearchControllerCreated];
4387 _voiceSearchController->PrepareToAppear();
4388}
4389
edchinc5720722017-08-14 22:06:314390#if !defined(NDEBUG)
4391- (void)viewSource {
4392 Tab* tab = [_model currentTab];
4393 DCHECK(tab);
4394 CRWWebController* webController = tab.webController;
4395 NSString* script = @"document.documentElement.outerHTML;";
4396 __weak Tab* weakTab = tab;
4397 __weak BrowserViewController* weakSelf = self;
4398 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4399 Tab* strongTab = weakTab;
4400 if (!strongTab)
4401 return;
4402 if (![result isKindOfClass:[NSString class]])
4403 result = @"Not an HTML page";
4404 std::string base64HTML;
4405 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4406 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194407 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314408 web::ReferrerPolicyDefault);
4409
4410 [[weakSelf tabModel]
4411 insertTabWithURL:URL
4412 referrer:referrer
4413 transition:ui::PAGE_TRANSITION_LINK
4414 opener:strongTab
4415 openedByDOM:YES
4416 atIndex:TabModelConstants::kTabPositionAutomatically
4417 inBackground:NO];
4418 };
4419 [webController executeJavaScript:script
4420 completionHandler:completionHandlerBlock];
4421}
4422#endif // !defined(NDEBUG)
4423
edchin2134c042017-08-18 13:57:354424// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4425- (void)showRateThisAppDialog {
4426 DCHECK(!_rateThisAppDialog);
4427
4428 // Store the current timestamp whenever this dialog is shown.
4429 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4430 base::Time::Now().ToInternalValue());
4431
Gregory Chatzinofff39ec5162017-10-05 20:28:534432 // iOS11 no longer supports the itms link to the app store. So, use a deep
4433 // link for iOS11 and the itms link for prior versions.
4434 NSURL* storeURL;
4435 if (base::ios::IsRunningOnIOS11OrLater()) {
4436 storeURL =
4437 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4438 @"google-chrome-the-fast-and-secure-web-browser/"
4439 @"id535886823?action=write-review")];
4440 } else {
4441 storeURL = [NSURL
4442 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4443 @"MZStore.woa/wa/"
4444 @"viewContentsUserReviews?type=Purple+Software&id="
4445 @"535886823&pt=9008&ct=rating")];
4446 }
edchin2134c042017-08-18 13:57:354447
4448 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014449 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354450
4451 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4452 [_rateThisAppDialog setAppStoreURL:storeURL];
4453 [_rateThisAppDialog setDelegate:self];
4454 [_rateThisAppDialog show];
4455}
4456
Gregory Chatzinoff3f40c1542017-08-30 07:50:044457- (void)showFindInPage {
4458 if (!self.canShowFindBar)
4459 return;
4460
4461 if (!_findBarController) {
4462 _findBarController =
4463 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4464 _findBarController.dispatcher = self.dispatcher;
4465 }
4466
4467 Tab* tab = [_model currentTab];
4468 DCHECK(tab);
4469 auto* helper = FindTabHelper::FromWebState(tab.webState);
4470 DCHECK(!helper->IsFindUIActive());
4471 helper->SetFindUIActive(true);
4472 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4473}
4474
4475- (void)closeFindInPage {
4476 __weak BrowserViewController* weakSelf = self;
4477 Tab* currentTab = [_model currentTab];
4478 if (currentTab) {
4479 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4480 [weakSelf updateFindBar:NO shouldFocus:NO];
4481 });
4482 }
4483}
4484
4485- (void)searchFindInPage {
4486 DCHECK([_model currentTab]);
4487 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4488 __weak BrowserViewController* weakSelf = self;
4489 helper->StartFinding(
4490 [_findBarController searchTerm], ^(FindInPageModel* model) {
4491 BrowserViewController* strongSelf = weakSelf;
4492 if (!strongSelf) {
4493 return;
4494 }
4495 [strongSelf->_findBarController updateResultsCount:model];
4496 });
4497
4498 if (!_isOffTheRecord)
4499 helper->PersistSearchTerm();
4500}
4501
4502- (void)findNextStringInPage {
4503 Tab* currentTab = [_model currentTab];
4504 DCHECK(currentTab);
4505 // TODO(crbug.com/603524): Reshow find bar if necessary.
4506 FindTabHelper::FromWebState(currentTab.webState)
4507 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4508 [_findBarController updateResultsCount:model];
4509 });
4510}
4511
4512- (void)findPreviousStringInPage {
4513 Tab* currentTab = [_model currentTab];
4514 DCHECK(currentTab);
4515 // TODO(crbug.com/603524): Reshow find bar if necessary.
4516 FindTabHelper::FromWebState(currentTab.webState)
4517 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4518 [_findBarController updateResultsCount:model];
4519 });
4520}
4521
edchinf84b2502017-08-31 21:30:454522- (void)showHelpPage {
4523 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4524 [self webPageOrderedOpen:helpUrl
4525 referrer:web::Referrer()
4526 inBackground:NO
4527 appendTo:kCurrentTab];
4528}
4529
edchinb59b5602017-09-01 15:00:204530- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104531 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204532 [self showAllBookmarks];
4533 } else {
4534 [self initializeBookmarkInteractionController];
4535 [_bookmarkInteractionController presentBookmarks];
4536 }
4537}
4538
edchin8ee0807d2017-09-01 23:52:474539- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104540 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474541 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4542 } else {
4543 if (!self.recentTabsCoordinator) {
4544 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4545 initWithBaseViewController:self];
4546 self.recentTabsCoordinator.loader = self;
4547 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4548 self.recentTabsCoordinator.browserState = _browserState;
4549 }
4550 [self.recentTabsCoordinator start];
4551 }
4552}
4553
Mark Cogan6de7e9a2017-09-06 12:57:214554- (void)requestDesktopSite {
4555 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4556}
4557
4558- (void)requestMobileSite {
4559 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4560}
4561
sdefresnee65fd872016-12-19 13:38:134562#pragma mark - Command Handling
4563
sdefresnee65fd872016-12-19 13:38:134564- (void)closeCurrentTab {
4565 Tab* currentTab = [_model currentTab];
4566 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4567 if (tabIndex == NSNotFound)
4568 return;
4569
jif7fed8122017-02-08 13:15:254570 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4571 // iPad.
sdefresnee65fd872016-12-19 13:38:134572 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4573 exitingPage.image =
4574 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4575
4576 // Close the actual tab, and add its image as a subview.
4577 [_model closeTabAtIndex:tabIndex];
4578
4579 // Do not animate close in iPad.
4580 if (!IsIPadIdiom()) {
4581 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524582 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134583 exitingPage, 0, YES, IsPortrait(), ^{
4584 [exitingPage removeFromSuperview];
4585 });
4586 }
4587}
4588
Elodie Banelaa5ab432017-09-28 14:42:014589- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4590 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434591 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134592 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4593 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014594 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464595 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014596 }
sdefresnee65fd872016-12-19 13:38:134597 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274598 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474599 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134600 if (_voiceSearchController)
4601 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364602
4603 Tab* currentTab = [_model currentTab];
4604 [currentTab dismissModals];
4605
rohitrao005a6432017-03-16 20:52:424606 if (currentTab) {
4607 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4608 if (findHelper) {
4609 findHelper->StopFinding(^{
4610 [self updateFindBar:NO shouldFocus:NO];
4611 });
4612 }
4613 }
rohitraob2bf3cb2017-02-10 14:10:364614
sdefresnee65fd872016-12-19 13:38:134615 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134616 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274617 _printController = nil;
sczsf1620e52017-10-02 22:54:464618 [_toolbarCoordinator dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134619 [_contextMenuCoordinator stop];
4620 [self dismissRateThisAppDialog];
4621
4622 if (self.presentedViewController) {
4623 // Dismisses any other modal controllers that may be present, e.g. Recent
4624 // Tabs.
4625 // Note that currently, some controllers like the bookmark ones were already
4626 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4627 // but are still reported as the presentedViewController. The result is that
4628 // this will call -dismissViewControllerAnimated:completion: a second time
4629 // on it. It is not per se an issue, as it is a no-op. The problem is that
4630 // in such a case, the completion block is not called.
4631 // To ensure the completion is called, nil is passed here, and the
4632 // completion is called below.
4633 [self dismissViewControllerAnimated:NO completion:nil];
4634 // Dismissed controllers will be so after a delay. Queue the completion
4635 // callback after that.
4636 if (completion) {
4637 dispatch_after(
4638 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4639 dispatch_get_main_queue(), ^{
4640 completion();
4641 });
4642 }
4643 } else if (completion) {
4644 // If no view controllers are presented, we should be ok with dispatching
4645 // the completion block directly.
4646 dispatch_async(dispatch_get_main_queue(), completion);
4647 }
4648}
4649
sdefresnee65fd872016-12-19 13:38:134650#pragma mark - Find Bar
4651
4652- (void)hideFindBarWithAnimation:(BOOL)animate {
4653 [_findBarController hideFindBarView:animate];
4654}
4655
4656- (void)showFindBarWithAnimation:(BOOL)animate
4657 selectText:(BOOL)selectText
4658 shouldFocus:(BOOL)shouldFocus {
4659 DCHECK(_findBarController);
4660 Tab* tab = [_model currentTab];
4661 DCHECK(tab);
4662 CRWWebController* webController = tab.webController;
4663
4664 CGRect referenceFrame = CGRectZero;
4665 if (IsIPadIdiom()) {
4666 referenceFrame = webController.visibleFrame;
4667 referenceFrame.origin.y -= kIPadFindBarOverlap;
4668 } else {
4669 referenceFrame = _contentArea.frame;
4670 }
4671
sczsf1620e52017-10-02 22:54:464672 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134673 [_findBarController addFindBarView:animate
4674 intoView:self.view
4675 withFrame:referenceFrame
4676 alignWithFrame:omniboxFrame
4677 selectText:selectText];
4678 [self updateFindBar:YES shouldFocus:shouldFocus];
4679}
4680
sdefresnee65fd872016-12-19 13:38:134681- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324682 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4683 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4684 // in a crash.
4685 if (![_model currentTab]) {
4686 return;
4687 }
rohitrao005a6432017-03-16 20:52:424688 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4689 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134690 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424691 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134692 }
4693
4694 [self setFramesForHeaders:[self headerViews]
4695 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424696 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134697 initialUpdate:initialUpdate
4698 focusTextfield:shouldFocus];
4699 } else {
4700 [self hideFindBarWithAnimation:YES];
4701 }
4702}
4703
4704- (void)showAllBookmarks {
4705 DCHECK(self.visible || self.dismissingModal);
4706 GURL URL(kChromeUIBookmarksURL);
4707 Tab* tab = [_model currentTab];
4708 web::NavigationManager::WebLoadParams params(URL);
4709 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234710 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134711}
4712
Gauthier Ambardf520c022017-08-29 07:42:234713- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134714 DCHECK(self.visible || self.dismissingModal);
4715 GURL url(kChromeUINewTabURL);
4716 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4717 if (fragment != "") {
4718 GURL::Replacements replacement;
4719 replacement.SetRefStr(fragment);
4720 url = url.ReplaceComponents(replacement);
4721 }
4722 Tab* tab = [_model currentTab];
4723 web::NavigationManager::WebLoadParams params(url);
4724 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234725 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134726}
4727
sdefresnee65fd872016-12-19 13:38:134728- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274729 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134730 base::RecordAction(base::UserMetricsAction(
4731 "IOSRateThisAppDialogDismissedProgramatically"));
4732 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274733 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134734 }
4735}
4736
Jean-François Geyelin5d2e184c2017-07-28 19:48:004737- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4738 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134739 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124740 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134741 _startVoiceSearchAfterNewTabAnimation = YES;
4742 return;
4743 }
4744
4745 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4746 // dismiss the keyboard.
4747 [self closeFindInPage];
4748 [[_model currentTab].webController dismissKeyboard];
4749
4750 // Ensure that voice search objects are created.
4751 [self ensureVoiceSearchControllerCreated];
4752 [self ensureVoiceSearchBarCreated];
4753
4754 // Present voice search.
4755 [_voiceSearchBar prepareToPresentVoiceSearch];
4756 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:464757 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:134758}
4759
4760#pragma mark - ToolbarOwner
4761
4762- (ToolbarController*)relinquishedToolbarController {
4763 if (_isToolbarControllerRelinquished)
4764 return nil;
4765
4766 ToolbarController* relinquishedToolbarController = nil;
sczsf1620e52017-10-02 22:54:464767 if ([_toolbarCoordinator view].hidden) {
sdefresnee65fd872016-12-19 13:38:134768 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:194769 if (currentTab.webState &&
4770 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
sdefresnee65fd872016-12-19 13:38:134771 // Use the native content controller's toolbar when the BVC's is hidden.
4772 id nativeController = [self nativeControllerForTab:currentTab];
4773 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4774 relinquishedToolbarController =
4775 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274776 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134777 }
4778 }
4779 } else {
Gauthier Ambard82c8cc52017-10-26 15:59:054780 relinquishedToolbarController = [_toolbarCoordinator toolbarController];
sdefresnee65fd872016-12-19 13:38:134781 }
4782 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4783 return relinquishedToolbarController;
4784}
4785
4786- (void)reparentToolbarController {
4787 if (_isToolbarControllerRelinquished) {
sczsf1620e52017-10-02 22:54:464788 if ([[_toolbarCoordinator view] isDescendantOfView:self.view]) {
sdefresnee65fd872016-12-19 13:38:134789 // A native content controller's toolbar has been relinquished.
4790 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274791 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134792 } else if ([_findBarController isFindInPageShown]) {
sczsf1620e52017-10-02 22:54:464793 [self.view insertSubview:[_toolbarCoordinator view]
sdefresnee65fd872016-12-19 13:38:134794 belowSubview:[_findBarController view]];
Jean-François Geyelined4cde72017-10-11 11:34:504795 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
4796 [self addConstraintsToToolbar];
4797 }
sdefresnee65fd872016-12-19 13:38:134798 } else {
sczsf1620e52017-10-02 22:54:464799 [self.view addSubview:[_toolbarCoordinator view]];
Jean-François Geyelined4cde72017-10-11 11:34:504800 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
4801 [self addConstraintsToToolbar];
4802 }
sdefresnee65fd872016-12-19 13:38:134803 }
sdefresnee65fd872016-12-19 13:38:134804 _isToolbarControllerRelinquished = NO;
4805 }
4806}
4807
4808#pragma mark - TabModelObserver methods
4809
4810// Observer method, tab inserted.
4811- (void)tabModel:(TabModel*)model
4812 didInsertTab:(Tab*)tab
4813 atIndex:(NSUInteger)modelIndex
4814 inForeground:(BOOL)fg {
4815 DCHECK(tab);
4816 [self installDelegatesForTab:tab];
4817
4818 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194819 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134820 }
4821}
4822
4823// Observer method, active tab changed.
4824- (void)tabModel:(TabModel*)model
4825 didChangeActiveTab:(Tab*)newTab
4826 previousTab:(Tab*)previousTab
4827 atIndex:(NSUInteger)index {
4828 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4829 // Currently this observer method is always called with a non-nil |newTab|,
4830 // but that may change in the future. Remove this DCHECK when it does.
4831 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274832 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304833 DCHECK(newTab.webState);
4834 infobars::InfoBarManager* infoBarManager =
4835 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134836 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4837 }
4838 [self updateVoiceSearchBarVisibilityAnimated:NO];
4839
Mohamad Ahmadi7d09ec32017-07-11 22:32:194840 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134841
4842 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134843}
4844
4845// Observer method, tab changed.
4846- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4847 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4848 if (tab == [_model currentTab]) {
4849 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134850 }
4851}
4852
sdefresne49cf2862017-03-15 13:46:144853// Observer method, tab replaced.
4854- (void)tabModel:(TabModel*)model
4855 didReplaceTab:(Tab*)oldTab
4856 withTab:(Tab*)newTab
4857 atIndex:(NSUInteger)index {
4858 [self uninstallDelegatesForTab:oldTab];
4859 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264860
michaeldo79909fb2017-05-09 23:42:504861 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304862 infobars::InfoBarManager* infoBarManager = nullptr;
4863 if (newTab) {
4864 DCHECK(newTab.webState);
4865 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4866 }
michaeldo79909fb2017-05-09 23:42:504867 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4868 }
4869
kkhorimotofa0844cc2017-03-20 17:01:264870 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4871 if (self.active && model.currentTab == newTab)
4872 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464873
4874 if (newTab)
4875 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144876}
4877
sdefresnee65fd872016-12-19 13:38:134878// A tab has been removed, remove its views from display if necessary.
4879- (void)tabModel:(TabModel*)model
4880 didRemoveTab:(Tab*)tab
4881 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144882 [self uninstallDelegatesForTab:tab];
4883
kkhorimoto496fdd72017-06-12 19:56:314884 // Cancel dialogs for |tab|'s WebState.
4885 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4886
sdefresnee65fd872016-12-19 13:38:134887 // Ignore changes while the tab stack view is visible (or while suspended).
4888 // The display will be refreshed when this view becomes active again.
4889 if (!self.visible || !model.webUsageEnabled)
4890 return;
4891
4892 // Remove the find bar for now.
4893 [self hideFindBarWithAnimation:NO];
4894}
4895
4896- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4897 if (tab == [model currentTab]) {
4898 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464899 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134900 }
4901
Mohamad Ahmadi7d09ec32017-07-11 22:32:194902 [_paymentRequestManager stopTrackingWebState:tab.webState];
4903
sdefresnee65fd872016-12-19 13:38:134904 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4905 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194906 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134907 }
4908}
4909
4910// Called when the number of tabs changes. Update the toolbar accordingly.
4911- (void)tabModelDidChangeTabCount:(TabModel*)model {
4912 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464913 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134914}
4915
4916#pragma mark - Upgrade Detection
4917
4918- (void)showUpgrade:(UpgradeCenter*)center {
4919 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274920 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134921 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304922 DCHECK(tab.webState);
4923 infobars::InfoBarManager* infoBarManager =
4924 InfoBarManagerImpl::FromWebState(tab.webState);
4925 DCHECK(infoBarManager);
4926 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134927 }
4928}
4929
sdefresnee65fd872016-12-19 13:38:134930
4931#pragma mark - InfoBarControllerDelegate
4932
4933- (void)infoBarContainerStateChanged:(bool)isAnimating {
4934 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4935 DCHECK(infoBarContainerView);
4936 CGRect containerFrame = infoBarContainerView.frame;
4937 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4938 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4939 containerFrame.size.height = height;
4940 BOOL isViewVisible = self.visible;
4941 [UIView animateWithDuration:0.1
4942 animations:^{
4943 [infoBarContainerView setFrame:containerFrame];
4944 }
4945 completion:^(BOOL finished) {
4946 if (!isViewVisible)
4947 return;
4948 UIAccessibilityPostNotification(
4949 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4950 }];
4951}
4952
4953- (BOOL)shouldAutorotate {
4954 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4955 // Don't rotate if a voice search is being presented or dismissed. Once the
4956 // transition animations finish, only the Voice Search UIViewController's
4957 // |-shouldAutorotate| will be called.
4958 return NO;
4959 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4960 // Don't auto rotate if side swipe controller view says not to.
4961 return NO;
4962 } else {
4963 return [super shouldAutorotate];
4964 }
4965}
4966
4967// Always return yes, as this tap should work with various recognizers,
4968// including UITextTapRecognizer, UILongPressGestureRecognizer,
4969// UIScrollViewPanGestureRecognizer and others.
4970- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4971 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4972 (UIGestureRecognizer*)otherGestureRecognizer {
4973 return YES;
4974}
4975
4976// Tap gestures should only be recognized within |_contentArea|.
4977- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4978 CGPoint location = [gesture locationInView:self.view];
4979
4980 // Only allow touches on descendant views of |_contentArea|.
4981 UIView* hitView = [self.view hitTest:location withEvent:nil];
4982 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4983}
4984
4985#pragma mark - SideSwipeController Delegate Methods
4986
4987- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4988 DCHECK(!IsIPadIdiom());
4989 // Update frame incase orientation changed while |_contentArea| was out of
4990 // the view hierarchy.
4991 [_contentArea setFrame:[sideSwipeView frame]];
4992
4993 [self.view insertSubview:_contentArea atIndex:0];
4994 [self updateVoiceSearchBarVisibilityAnimated:NO];
4995 [self updateToolbar];
4996
4997 // Reset horizontal stack view.
4998 [sideSwipeView removeFromSuperview];
4999 [_sideSwipeController setInSwipe:NO];
5000 [_infoBarContainer->view() setHidden:NO];
5001}
5002
5003- (UIView*)contentView {
5004 return _contentArea;
5005}
5006
sdefresnee65fd872016-12-19 13:38:135007- (BOOL)preventSideSwipe {
sczsf1620e52017-10-02 22:54:465008 if ([_toolbarCoordinator toolsPopupController])
sdefresnee65fd872016-12-19 13:38:135009 return YES;
5010
5011 if (_voiceSearchController && _voiceSearchController->IsVisible())
5012 return YES;
5013
sdefresnee65fd872016-12-19 13:38:135014 if (!self.active)
5015 return YES;
5016
5017 return NO;
5018}
5019
5020- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5021 if (visible) {
5022 [self updateVoiceSearchBarVisibilityAnimated:NO];
5023 [self updateToolbar];
5024 [_infoBarContainer->view() setHidden:NO];
5025 } else {
5026 // Hide UI accessories such as find bar and first visit overlays
5027 // for welcome page.
5028 [self hideFindBarWithAnimation:NO];
5029 [_infoBarContainer->view() setHidden:YES];
5030 [_voiceSearchBar setHidden:YES];
5031 }
5032}
5033
5034- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5035 BOOL seenToolbar = NO;
5036 BOOL seenInfoBarContainer = NO;
5037 BOOL seenContentArea = NO;
5038 for (UIView* view in views.subviews) {
sczsf1620e52017-10-02 22:54:465039 if (view == [_toolbarCoordinator view])
sdefresnee65fd872016-12-19 13:38:135040 seenToolbar = YES;
5041 else if (view == _infoBarContainer->view())
5042 seenInfoBarContainer = YES;
5043 else if (view == _contentArea)
5044 seenContentArea = YES;
5045 if ((seenToolbar && !seenInfoBarContainer) ||
5046 (seenInfoBarContainer && !seenContentArea))
5047 return NO;
5048 }
5049 return YES;
5050}
5051
5052#pragma mark - PreloadControllerDelegate methods
5053
rohitraoeeb5293b2017-06-15 14:40:025054- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445055 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135056}
5057
rohitraoeeb5293b2017-06-15 14:40:025058- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5059 return [self hasControllerForURL:url];
5060}
5061
sdefresnee65fd872016-12-19 13:38:135062#pragma mark - BookmarkBridgeMethods
5063
5064// If an added or removed bookmark is the same as the current url, update the
5065// toolbar so the star highlight is kept in sync.
5066- (void)bookmarkNodeModified:(const BookmarkNode*)node {
Sylvain Defresnee7f2c8a2017-10-17 02:39:195067 if ([_model currentTab].webState &&
5068 node->url() == [_model currentTab].webState->GetLastCommittedURL()) {
sdefresnee65fd872016-12-19 13:38:135069 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255070 }
sdefresnee65fd872016-12-19 13:38:135071}
5072
5073// If all bookmarks are removed, update the toolbar so the star highlight is
5074// kept in sync.
5075- (void)allBookmarksRemoved {
5076 [self updateToolbar];
5077}
5078
sdefresnee65fd872016-12-19 13:38:135079- (void)showErrorAlertWithStringTitle:(NSString*)title
5080 message:(NSString*)message {
5081 // Dismiss current alert.
5082 [_alertCoordinator stop];
5083
stkhapuginc9eee7b2017-04-10 15:49:275084 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5085 message:message
5086 viewController:self];
sdefresnee65fd872016-12-19 13:38:135087 [_alertCoordinator start];
5088}
5089
edchineeb4d422017-10-02 17:39:365090- (void)showSnackbar:(NSString*)text {
5091 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5092 message.accessibilityLabel = text;
5093 message.duration = 2.0;
5094 message.category = kBrowserViewControllerSnackbarCategory;
5095 [self.dispatcher showSnackbarMessage:message];
5096}
5097
sdefresnee65fd872016-12-19 13:38:135098#pragma mark - Show Mail Composer methods
5099
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575100- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5101 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135102 if (![MFMailComposeViewController canSendMail]) {
5103 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575104 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135105 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575106 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135107 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5108 return;
5109 }
stkhapuginc9eee7b2017-04-10 15:49:275110 MFMailComposeViewController* mailViewController =
5111 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135112 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575113 [mailViewController setToRecipients:[context toRecipients]];
5114 [mailViewController setSubject:[context subject]];
5115 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135116
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575117 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135118 if (!textFile.empty()) {
5119 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5120 NSData* data = [NSData dataWithContentsOfFile:filename];
5121 if (data) {
5122 NSString* displayName =
5123 base::SysUTF8ToNSString(textFile.BaseName().value());
5124 [mailViewController addAttachmentData:data
5125 mimeType:@"text/plain"
5126 fileName:displayName];
5127 }
5128 }
5129
5130 [mailViewController setMailComposeDelegate:self];
5131 [self presentViewController:mailViewController animated:YES completion:nil];
5132}
5133
5134#pragma mark - MFMailComposeViewControllerDelegate methods
5135
5136- (void)mailComposeController:(MFMailComposeViewController*)controller
5137 didFinishWithResult:(MFMailComposeResult)result
5138 error:(NSError*)error {
5139 [self dismissViewControllerAnimated:YES completion:nil];
5140}
5141
5142#pragma mark - StoreKitLauncher methods
5143
5144- (void)productViewControllerDidFinish:
5145 (SKStoreProductViewController*)viewController {
5146 [self dismissViewControllerAnimated:YES completion:nil];
5147}
5148
5149- (void)openAppStore:(NSString*)appId {
5150 if (![appId length])
5151 return;
5152 NSDictionary* product =
5153 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275154 SKStoreProductViewController* storeViewController =
5155 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135156 [storeViewController setDelegate:self];
5157 [storeViewController loadProductWithParameters:product completionBlock:nil];
5158 [self presentViewController:storeViewController animated:YES completion:nil];
5159}
5160
5161#pragma mark - TabDialogDelegate methods
5162
sdefresnee65fd872016-12-19 13:38:135163- (void)cancelDialogForTab:(Tab*)tab {
5164 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5165}
5166
5167#pragma mark - FKFeedbackPromptDelegate methods
5168
5169- (void)userTappedRateApp:(UIView*)view {
5170 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275171 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135172}
5173
5174- (void)userTappedSendFeedback:(UIView*)view {
5175 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275176 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205177 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135178}
5179
5180- (void)userTappedDismiss:(UIView*)view {
5181 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275182 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135183}
5184
5185#pragma mark - VoiceSearchBarDelegate
5186
5187- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275188 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135189 [self ensureVoiceSearchControllerCreated];
5190 return _voiceSearchController->IsTextToSpeechEnabled() &&
5191 _voiceSearchController->IsTextToSpeechSupported();
5192}
5193
5194- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275195 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135196 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5197}
5198
5199#pragma mark - VoiceSearchPresenter
5200
5201- (UIView*)voiceSearchButton {
5202 return _voiceSearchButton;
5203}
5204
5205- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5206 return [self currentLogoAnimationControllerOwner];
5207}
5208
Rohit Rao01e0e002017-08-14 20:49:435209#pragma mark - ActivityService Providers
5210
5211- (void)presentActivityServiceViewController:(UIViewController*)controller {
5212 [self presentViewController:controller animated:YES completion:nil];
5213}
5214
5215- (void)activityServiceDidEndPresenting {
5216 self.presenting = NO;
5217 [self.dialogPresenter tryToPresent];
5218}
5219
Rohit Raocda0a992017-08-16 15:37:115220#pragma mark - QRScanner Requirements
5221
5222- (void)presentQRScannerViewController:(UIViewController*)controller {
5223 [self presentViewController:controller animated:YES completion:nil];
5224}
5225
5226- (void)dismissQRScannerViewController:(UIViewController*)controller
5227 completion:(void (^)(void))completion {
5228 DCHECK_EQ(controller, self.presentedViewController);
5229 [self dismissViewControllerAnimated:YES completion:completion];
5230}
5231
sczsdd860eba2017-08-10 01:55:385232#pragma mark - TabHistoryPresenter
5233
sczs0a726d22017-08-21 22:40:135234- (UIView*)viewForTabHistoryPresentation {
5235 return self.view;
5236}
5237
sczsdd860eba2017-08-10 01:55:385238- (void)prepareForTabHistoryPresentation {
5239 DCHECK(self.visible || self.dismissingModal);
5240 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465241 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385242}
5243
Mike Doughertya1ec26402017-08-23 19:46:315244#pragma mark - CaptivePortalDetectorTabHelperDelegate
5245
5246- (void)captivePortalBlockingPage:(IOSCaptivePortalBlockingPage*)blockingPage
5247 connectWithLandingURL:(GURL)landingURL {
5248 _captivePortalLoginCoordinator = [[CaptivePortalLoginCoordinator alloc]
5249 initWithBaseViewController:self
5250 landingURL:landingURL];
5251 [_captivePortalLoginCoordinator start];
5252}
5253
Gregory Chatzinoffdf93d692017-09-09 01:32:275254#pragma mark - PageInfoPresentation
5255
Gregory Chatzinoffb6a01f72017-09-20 20:06:395256- (void)presentPageInfoView:(UIView*)pageInfoView {
5257 [pageInfoView setFrame:self.view.bounds];
5258 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275259}
5260
5261- (void)prepareForPageInfoPresentation {
5262 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465263 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275264}
5265
Gregory Chatzinoffb6a01f72017-09-20 20:06:395266- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5267 return [self.view convertPoint:origin fromView:nil];
5268}
5269
Sylvain Defresnecacc3a52017-09-12 13:51:045270#pragma mark - WebStatePrinter
5271
5272- (void)printWebState:(web::WebState*)webState {
5273 if (webState == [_model currentTab].webState)
5274 [self printTab];
5275}
5276
Eugene But35ded552017-09-13 23:31:595277#pragma mark - RepostFormTabHelperDelegate
5278
5279- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
5280 presentRepostFromDialogAtPoint:(CGPoint)location
5281 completionHandler:(void (^)(BOOL))completion {
5282 _repostFormCoordinator = [[RepostFormCoordinator alloc]
5283 initWithBaseViewController:self
5284 dialogLocation:location
5285 webState:helper->web_state()
5286 completionHandler:completion];
5287 [_repostFormCoordinator start];
5288}
5289
5290- (void)repostFormTabHelperDismissRepostFormDialog:
5291 (RepostFormTabHelper*)helper {
5292 _repostFormCoordinator = nil;
5293}
5294
edchinf5150c682017-09-18 02:50:035295#pragma mark - TabStripPresentation
5296
5297- (BOOL)isTabStripFullyVisible {
5298 return ([self currentHeaderOffset] == 0.0f);
5299}
5300
5301- (void)showTabStripView:(UIView*)tabStripView {
5302 DCHECK([self isViewLoaded]);
5303 DCHECK(tabStripView);
5304 self.tabStripView = tabStripView;
5305 CGRect tabStripFrame = [self.tabStripView frame];
5306 tabStripFrame.origin = CGPointZero;
5307 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5308 // because the CGPointZero above will break reset the offset, but it's not
5309 // clear what removing that will do.
5310 tabStripFrame.origin.y = [self headerOffset];
5311 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5312 [self.tabStripView setFrame:tabStripFrame];
5313 [[self view] addSubview:tabStripView];
5314}
5315
edchincd32fdf2017-10-25 12:45:455316#pragma mark - ManageAccountsDelegate
5317
5318- (void)onManageAccounts {
5319 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5320 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5321 ->GetState());
edchin5b8aa052017-10-30 23:27:285322 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455323}
5324
5325- (void)onAddAccount {
5326 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5327 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5328 ->GetState());
5329 [self.dispatcher showAddAccount];
5330}
5331
5332- (void)onGoIncognito:(const GURL&)url {
5333 // The user taps on go incognito from the mobile U-turn webpage (the web page
5334 // that displays all users accounts available in the content area). As the
5335 // user chooses to go to incognito, the mobile U-turn page is no longer
5336 // neeeded. The current solution is to go back in history. This has the
5337 // advantage of keeping the current browsing session and give a good user
5338 // experience when the user comes back from incognito.
5339 [self.tabModel.currentTab goBack];
5340
5341 if (url.is_valid()) {
5342 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5343 initWithURL:url
5344 referrer:web::Referrer() // Strip referrer when switching modes.
5345 inIncognito:YES
5346 inBackground:NO
5347 appendTo:kLastTab];
5348 [self.dispatcher openURL:command];
5349 } else {
5350 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5351 }
5352}
5353
sdefresnee65fd872016-12-19 13:38:135354@end