blob: 8e25cc99d0c2accab82d26e0c060ec2b04897127 [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"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10144#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13145#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42146#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27148#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
149#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13150#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37151#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13152#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11153#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
154#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13155#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03156#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13157#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13158#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53159#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39160#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13161#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin7f210cd2017-09-28 08:03:53162#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13163#import "ios/chrome/browser/ui/stack_view/card_view.h"
164#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
165#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13166#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03167#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
168#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
169#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
Jean-François Geyelined4cde72017-10-11 11:34:50170#import "ios/chrome/browser/ui/toolbar/toolbar_controller_base_feature.h"
sczsf1620e52017-10-02 22:54:46171#include "ios/chrome/browser/ui/toolbar/toolbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13172#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
173#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sczsc2b4f152017-10-11 01:44:24174#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
sczsbbad1632017-07-29 03:48:00175#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13176#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
177#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
178#include "ios/chrome/browser/ui/ui_util.h"
179#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28180#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13181#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
182#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51183#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13184#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27185#import "ios/chrome/browser/web/load_timing_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13186#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04187#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05188#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59189#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53190#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04191#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05192#import "ios/chrome/browser/web_state_list/web_state_list.h"
193#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57194#import "ios/chrome/browser/webui/net_export_tab_helper.h"
195#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
196#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13197#include "ios/chrome/grit/ios_chromium_strings.h"
198#include "ios/chrome/grit/ios_strings.h"
199#import "ios/net/request_tracker.h"
200#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
201#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
202#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
203#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
204#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
205#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
206#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
207#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36208#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13209#include "ios/web/public/navigation_item.h"
210#import "ios/web/public/navigation_manager.h"
211#include "ios/web/public/referrer_util.h"
212#include "ios/web/public/ssl_status.h"
213#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39214#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13215#include "ios/web/public/web_client.h"
216#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13217#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29218#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19219#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13220#import "ios/web/public/web_state/web_state_delegate_bridge.h"
221#include "ios/web/public/web_thread.h"
222#import "ios/web/web_state/ui/crw_web_controller.h"
223#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17224#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13225#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
226#include "net/ssl/ssl_info.h"
227#include "net/url_request/url_request_context_getter.h"
228#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
229#include "ui/base/l10n/l10n_util.h"
230#include "ui/base/l10n/l10n_util_mac.h"
231#include "ui/base/page_transition_types.h"
232#include "url/gurl.h"
233
stkhapuginf58b10d02017-04-10 13:36:17234#if !defined(__has_feature) || !__has_feature(objc_arc)
235#error "This file requires ARC support."
236#endif
237
sdefresnee65fd872016-12-19 13:38:13238using base::UserMetricsAction;
239using bookmarks::BookmarkNode;
240
241class BrowserBookmarkModelBridge;
242class InfoBarContainerDelegateIOS;
243
sdefresnee65fd872016-12-19 13:38:13244namespace {
245
246typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
247 // Note: these values must match the ContextMenuOption enum in histograms.xml.
248 ACTION_OPEN_IN_NEW_TAB = 0,
249 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
250 ACTION_COPY_LINK_ADDRESS = 2,
251 ACTION_SAVE_IMAGE = 6,
252 ACTION_OPEN_IMAGE = 7,
253 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
254 ACTION_SEARCH_BY_IMAGE = 11,
255 ACTION_OPEN_JAVASCRIPT = 21,
256 ACTION_READ_LATER = 22,
257 NUM_ACTIONS = 23,
258};
259
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28260void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13261 if (is_image) {
262 if (is_link) {
263 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
264 NUM_ACTIONS);
265 } else {
266 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
267 NUM_ACTIONS);
268 }
269 } else {
270 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
271 NUM_ACTIONS);
272 }
273}
274
edchinf5150c682017-09-18 02:50:03275// Returns the status bar background color.
276UIColor* StatusBarBackgroundColor() {
277 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
278}
279
280// Duration of the toolbar animation.
281const NSTimeInterval kFullScreenControllerToolbarAnimationDuration = 0.3;
282
sdefresnee65fd872016-12-19 13:38:13283const CGFloat kVoiceSearchBarHeight = 59.0;
284
285// Dimensions to use when downsizing an image for search-by-image.
286const CGFloat kSearchByImageMaxImageArea = 90000.0;
287const CGFloat kSearchByImageMaxImageWidth = 600.0;
288const CGFloat kSearchByImageMaxImageHeight = 400.0;
289
sdefresnee65fd872016-12-19 13:38:13290enum HeaderBehaviour {
291 // The header moves completely out of the screen.
292 Hideable = 0,
293 // This header stays on screen and doesn't overlap with the content.
294 Visible,
295 // This header stay on screen and covers part of the content.
296 Overlap
297};
298
sdefresnee65fd872016-12-19 13:38:13299const CGFloat kIPadFindBarOverlap = 11;
300
301bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24302 // Most URLs are allowed in incognito; the following is an exception.
303 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13304}
305
edchineeb4d422017-10-02 17:39:36306// Snackbar category for browser view controller.
307NSString* const kBrowserViewControllerSnackbarCategory =
308 @"BrowserViewControllerSnackbarCategory";
309
rohitrao005a6432017-03-16 20:52:42310} // namespace
sdefresnee65fd872016-12-19 13:38:13311
stkhapugin952ecef2017-04-11 12:11:45312#pragma mark - HeaderDefinition helper
313
314@interface HeaderDefinition : NSObject
315
316// The header view.
317@property(nonatomic, strong) UIView* view;
318// How to place the view, and its behaviour when the headers move.
319@property(nonatomic, assign) HeaderBehaviour behaviour;
320// Reduces the height of a header to adjust for shadows.
321@property(nonatomic, assign) CGFloat heightAdjustement;
322// Nudges that particular header up by this number of points.
323@property(nonatomic, assign) CGFloat inset;
324
325- (instancetype)initWithView:(UIView*)view
326 headerBehaviour:(HeaderBehaviour)behaviour
327 heightAdjustment:(CGFloat)heightAdjustment
328 inset:(CGFloat)inset;
329
330+ (instancetype)definitionWithView:(UIView*)view
331 headerBehaviour:(HeaderBehaviour)behaviour
332 heightAdjustment:(CGFloat)heightAdjustment
333 inset:(CGFloat)inset;
334
335@end
336
337@implementation HeaderDefinition
338@synthesize view = _view;
339@synthesize behaviour = _behaviour;
340@synthesize heightAdjustement = _heightAdjustement;
341@synthesize inset = _inset;
342
343+ (instancetype)definitionWithView:(UIView*)view
344 headerBehaviour:(HeaderBehaviour)behaviour
345 heightAdjustment:(CGFloat)heightAdjustment
346 inset:(CGFloat)inset {
347 return [[self alloc] initWithView:view
348 headerBehaviour:behaviour
349 heightAdjustment:heightAdjustment
350 inset:inset];
351}
352
353- (instancetype)initWithView:(UIView*)view
354 headerBehaviour:(HeaderBehaviour)behaviour
355 heightAdjustment:(CGFloat)heightAdjustment
356 inset:(CGFloat)inset {
357 self = [super init];
358 if (self) {
359 _view = view;
360 _behaviour = behaviour;
361 _heightAdjustement = heightAdjustment;
362 _inset = inset;
363 }
364 return self;
365}
366
367@end
368
369#pragma mark - BVC
370
Rohit Rao01e0e002017-08-14 20:49:43371@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43372 AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13373 CRWNativeContentProvider,
374 CRWWebStateDelegate,
375 DialogPresenterDelegate,
376 FullScreenControllerDelegate,
Mike Doughertya1ec26402017-08-23 19:46:31377 IOSCaptivePortalBlockingPageDelegate,
sdefresnee65fd872016-12-19 13:38:13378 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57379 NetExportTabHelperDelegate,
edchincd32fdf2017-10-25 12:45:45380 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13381 MFMailComposeViewControllerDelegate,
382 NewTabPageControllerObserver,
383 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27384 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13385 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55386 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13387 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11388 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59389 RepostFormTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13390 SKStoreProductViewControllerDelegate,
391 SnapshotOverlayProvider,
392 StoreKitLauncher,
393 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19394 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38395 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13396 TabModelObserver,
397 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03398 TabStripPresentation,
sdefresnee65fd872016-12-19 13:38:13399 UIGestureRecognizerDelegate,
400 UpgradeCenterClientProtocol,
401 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04402 VoiceSearchBarOwner,
403 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13404 // The dependency factory passed on initialization. Used to vend objects used
405 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27406 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13407
408 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27409 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13410
sczsf1620e52017-10-02 22:54:46411 // Facade objects used by |_toolbarCoordinator|.
412 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13413 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
414 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
415
sdefresnee65fd872016-12-19 13:38:13416 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27417 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13418
Mike Doughertya1ec26402017-08-23 19:46:31419 // Handles displaying the captive portal login page.
420 CaptivePortalLoginCoordinator* _captivePortalLoginCoordinator;
421
sdefresnee65fd872016-12-19 13:38:13422 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27423 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13424
425 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27426 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13427
428 // Handles presentation of JavaScript dialogs.
429 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
430
justincohen75011c32017-04-28 16:31:39431 // Handles command dispatching.
432 CommandDispatcher* _dispatcher;
433
sdefresnee65fd872016-12-19 13:38:13434 // Keyboard commands provider. It offloads most of the keyboard commands
435 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27436 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13437
438 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
439 // to |-reparentToolbarController| will reset it to NO.
440 BOOL _isToolbarControllerRelinquished;
441
442 // The controller that owns the currently relinquished toolbar controller.
443 // The reference is weak because it's possible for the toolbar owner to be
444 // deallocated mid-animation due to memory pressure or a tab being closed
445 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27446 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13447
sdefresnee65fd872016-12-19 13:38:13448 // Used to inject Javascript implementing the PaymentRequest API and to
449 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27450 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13451
sdefresnee65fd872016-12-19 13:38:13452 // Used to display the Voice Search UI. Nil if not visible.
453 scoped_refptr<VoiceSearchController> _voiceSearchController;
454
gambard6299cc1d2017-02-21 13:06:03455 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27456 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03457
sdefresnee65fd872016-12-19 13:38:13458 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27459 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13460
sdefresnee65fd872016-12-19 13:38:13461 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27462 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13463
464 // Records the set of domains for which full screen alert has already been
465 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27466 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13467
468 // Adapter to let BVC be the delegate for WebState.
469 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
470
471 // YES if new tab is animating in.
472 BOOL _inNewTabAnimation;
473
474 // YES if Voice Search should be started when the new tab animation is
475 // finished.
476 BOOL _startVoiceSearchAfterNewTabAnimation;
477
478 // YES if the user interacts with the location bar.
479 BOOL _locationBarHasFocus;
480 // YES if a load was cancelled due to typing in the location bar.
481 BOOL _locationBarEditCancelledLoad;
482 // YES if waiting for a foreground tab due to expectNewForegroundTab.
483 BOOL _expectingForegroundTab;
484
Sylvain Defresne41170aa2017-06-15 10:25:20485 // Whether or not -shutdown has been called.
486 BOOL _isShutdown;
487
sdefresnee65fd872016-12-19 13:38:13488 // The ChromeBrowserState associated with this BVC.
489 ios::ChromeBrowserState* _browserState; // weak
490
491 // Whether or not Incognito* is enabled.
492 BOOL _isOffTheRecord;
493
494 // The last point within |_contentArea| that's received a touch.
495 CGPoint _lastTapPoint;
496
497 // The time at which |_lastTapPoint| was most recently set.
498 CFTimeInterval _lastTapTime;
499
500 // A single infobar container handles all infobars in all tabs. It keeps
501 // track of infobars for current tab (accessed via infobar helper of
502 // the current tab).
503 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
504
505 // Bridge class to deliver container change notifications to BVC.
506 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
507
508 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37509 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27510 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13511
512 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11513 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13514
sdefresnee65fd872016-12-19 13:38:13515 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27516 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13517
518 // Bridge to register for bookmark changes.
519 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
520
521 // Cached pointer to the bookmarks model.
522 bookmarks::BookmarkModel* _bookmarkModel; // weak
523
524 // The controller that shows the bookmarking UI after the user taps the star
525 // button.
stkhapuginc9eee7b2017-04-10 15:49:27526 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13527
sdefresnee65fd872016-12-19 13:38:13528 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27529 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13530
Eugene But56efc322017-08-11 14:03:44531 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28532 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13533
534 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27535 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13536
Jean-François Geyelin3d47c212017-08-03 09:24:09537 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27538 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13539
Rohit Rao01e0e002017-08-14 20:49:43540 // Coordinator for the share menu (Activity Services).
541 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
542
sdefresnee65fd872016-12-19 13:38:13543 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27544 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38545
Rohit Raocda0a992017-08-16 15:37:11546 // Coordinator for the QR scanner.
547 QRScannerLegacyCoordinator* _qrScannerCoordinator;
548
sczsdd860eba2017-08-10 01:55:38549 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13550 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53551
552 // Coordinator for displaying Sad Tab.
553 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27554
555 // Coordinator for Page Info UI.
556 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59557
558 // Coordinator for displaying Repost Form dialog.
559 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22560
edchin7f210cd2017-09-28 08:03:53561 // Coordinator for displaying snackbars.
562 SnackbarCoordinator* _snackbarCoordinator;
563
sczsf1620e52017-10-02 22:54:46564 // Coordinator for the toolbar.
565 LegacyToolbarCoordinator* _toolbarCoordinator;
566
Louis Romerod11747a2017-10-20 20:10:35567 // Coordinator for the External Search UI.
568 ExternalSearchCoordinator* _externalSearchCoordinator;
569
Justin Cohenb3170c32017-09-19 01:55:22570 // Fake status bar view used to blend the toolbar into the status bar.
571 UIView* _fakeStatusBarView;
sdefresnee65fd872016-12-19 13:38:13572}
573
574// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17575@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13576// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17577@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13578// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17579@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13580// Whether the current tab can enable the request desktop menu item.
581@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
582// Whether the sharing menu should be enabled.
583@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
584// Helper method to check web controller canShowFindBar method.
585@property(nonatomic, assign, readonly) BOOL canShowFindBar;
586// Whether the controller's view is currently available.
587// YES from viewWillAppear to viewWillDisappear.
588@property(nonatomic, assign, getter=isVisible) BOOL visible;
589// Whether the controller's view is currently visible.
590// YES from viewDidAppear to viewWillDisappear.
591@property(nonatomic, assign) BOOL viewVisible;
592// Whether the controller is currently dismissing a presented view controller.
593@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
594// Returns YES if the toolbar has not been scrolled out by fullscreen.
595@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
596 BOOL toolbarOnScreen;
597// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12598@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13599// Whether BVC prefers to hide the status bar. This value is used to determine
600// the response from the |prefersStatusBarHidden| method.
601@property(nonatomic, assign) BOOL hideStatusBar;
602// Whether the VoiceSearchBar should be displayed.
603@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
604// Coordinator for displaying a modal overlay with activity indicator to prevent
605// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17606@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13607 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39608// A block to be run when the |tabWasAdded:| method completes the animation
609// for the presentation of a new tab. Can be used to record performance metrics.
610@property(nonatomic, strong, nullable)
611 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42612// Coordinator for Recent Tabs.
613@property(nonatomic, strong)
614 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03615// Coordinator for tablet tab strip.
616@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
617// A weak reference to the view of the tab strip on tablet.
618@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13619
liaoyukeea9f3ee62017-03-07 22:05:39620// The user agent type used to load the currently visible page. User agent type
621// is NONE if there is no visible page or visible page is a native page.
622@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
623
stkhapugin952ecef2017-04-11 12:11:45624// Returns the header views, all the chrome on top of the page, including the
625// ones that cannot be scrolled off screen by full screen.
626@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
627
Cooper Knaakd0a974cd2017-08-10 18:05:47628// Used to display the new tab tip in-product help promotion bubble. |nil| if
629// the new tab tip bubble has not yet been presented. Once the bubble is
630// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38631@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47632 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38633
Helen Yang9175bd52017-08-12 00:28:40634// Used to display the new incognito tab tip in-product help promotion bubble.
635@property(nonatomic, strong)
636 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
637
sdefresnee65fd872016-12-19 13:38:13638// BVC initialization:
639// If the BVC is initialized with a valid browser state & tab model immediately,
640// the path is straightforward: functionality is enabled, and the UI is built
641// when -viewDidLoad is called.
642// If the BVC is initialized without a browser state or tab model, the tab model
643// and browser state may or may not be provided before -viewDidLoad is called.
644// In most cases, they will not, to improve startup performance.
645// In order to handle this, initialization of various aspects of BVC have been
646// broken out into the following functions, which have expectations (enforced
647// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
648
649// Registers for notifications.
650- (void)registerForNotifications;
651// Called when a tab is starting to load. If it's a link click or form
652// submission, the user is navigating away from any entries in the forward
653// history. Tell the toolbar so it can update the UI appropriately.
654// See the warning on [Tab webWillStartLoadingURL] about invocation of this
655// method sequence by malicious pages.
656- (void)pageLoadStarting:(NSNotification*)notify;
657// Called when a tab actually starts loading.
658- (void)pageLoadStarted:(NSNotification*)notify;
659// Called when a tab finishes loading. Update the Omnibox with the url and
660// stop any page load progess display.
661- (void)pageLoadComplete:(NSNotification*)notify;
662// Called when a tab is deselected in the model.
663// This notification also occurs when a tab is closed.
664- (void)tabDeselected:(NSNotification*)notify;
665// Animates sliding current tab and rotate-entering new tab while new tab loads
666// in background on the iPhone only.
667- (void)tabWasAdded:(NSNotification*)notify;
668
669// Updates non-view-related functionality with the given browser state and tab
670// model.
671// Does not matter whether or not the view has been loaded.
672- (void)updateWithTabModel:(TabModel*)model
673 browserState:(ios::ChromeBrowserState*)browserState;
674// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
675// the status bar to mimic this layout.
676- (void)installFakeStatusBar;
677// Builds the UI parts of tab strip and the toolbar. Does not matter whether
678// or not browser state and tab model are valid.
679- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50680// Sets up the constraints on the toolbar.
681- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13682// Updates view-related functionality with the given tab model and browser
683// state. The view must have been loaded. Uses |_browserState| and |_model|.
684- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22685// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13686- (void)setUpViewLayout;
sdefresnee65fd872016-12-19 13:38:13687// Makes |tab| the currently visible tab, displaying its view. Calls
688// -selectedTabChanged on the toolbar only if |newSelection| is YES.
689- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
690// Initializes the bookmark interaction controller if not already initialized.
691- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13692// Add all delegates to the provided |tab|.
693- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14694// Remove delegates from the provided |tab|.
695- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13696// Closes the current tab, with animation if applicable.
697- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13698// Show the bookmarks page.
699- (void)showAllBookmarks;
700// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23701- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13702// Dismisses the "rate this app" dialog.
703- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32704// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13705- (BOOL)isTabNativePage:(Tab*)tab;
706// Returns the view to use when animating a page in or out, positioning it to
707// fill the content area but not actually adding it to the view hierarchy.
708- (UIImageView*)pageOpenCloseAnimationView;
709// Returns the view to use when animating full screen NTP paper in, filling the
710// entire screen but not actually adding it to the view hierarchy.
711- (UIImageView*)pageFullScreenOpenCloseAnimationView;
712// Updates the toolbar display based on the current tab.
713- (void)updateToolbar;
714// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12715// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13716- (void)updateDialogPresenterActiveState;
717// Dismisses popups and modal dialogs that are displayed above the BVC upon size
718// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
719// is performed.
720// TODO(crbug.com/522721): Support size changes for all popups and modal
721// dialogs.
722- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47723
724// Returns a bubble associated with an in-product help promotion if
725// it is valid to show the promotion and |nil| otherwise. |feature| is the
726// base::Feature object associated with the given promotion. |direction| is the
727// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21728// arrow on the button. |text| is the text displayed by the bubble. This method
729// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47730- (BubbleViewControllerPresenter*)
731bubblePresenterForFeature:(const base::Feature&)feature
732 direction:(BubbleArrowDirection)direction
733 alignment:(BubbleAlignment)alignment
734 text:(NSString*)text;
735
Cooper Knaak120cee5e2017-08-10 20:57:00736// Waits to present a bubble associated with the new tab tip in-product help
737// promotion until the feature engagement tracker database is fully initialized.
738// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
739// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11740// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21741// determines it is not valid to present it. This method requires that
742// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00743- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11744// Optionally presents a bubble associated with the new tab tip in-product help
745// promotion. If the feature engagement tracker determines it is valid to show
746// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
747// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21748// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
749// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00750- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40751// Waits to present a bubble associated with the new incognito tab tip
752// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21753// fully initialized. This method requires that |self.browserState| is
754// not NULL.
Helen Yang9175bd52017-08-12 00:28:40755- (void)presentNewIncognitoTabTipBubbleOnInitialized;
756// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21757// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40758- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21759// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
760// eligible. Only one can be eligible per session (as enforced by the
761// FeatureEngagementTracker). If neither is eligible, neither bubble is
762// presented. This method requires that |self.browserState| is not NULL.
763- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00764
sdefresnee65fd872016-12-19 13:38:13765// Update find bar with model data. If |shouldFocus| is set to YES, the text
766// field will become first responder.
767- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13768// Hide find bar.
769- (void)hideFindBarWithAnimation:(BOOL)animate;
770// Shows find bar. If |selectText| is YES, all text inside the Find Bar
771// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
772// set to be first responder.
773- (void)showFindBarWithAnimation:(BOOL)animate
774 selectText:(BOOL)selectText
775 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36776
sdefresnee65fd872016-12-19 13:38:13777// The infobar state (typically height) has changed.
778- (void)infoBarContainerStateChanged:(bool)is_animating;
779// Adds a CardView on top of the contentArea either taking the size of the full
780// screen or just the size of the space under the header.
781// Returns the CardView that was added.
782- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
783// Called when either a tab finishes loading or when a tab with finished content
784// is added directly to the model via pre-rendering. The tab must be non-nil and
785// must be a member of the tab model controlled by this BrowserViewController.
786- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
787// Evaluates Javascript asynchronously using the current page context.
788- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36789// Shows a self-dismissing snackbar displaying |message|.
790- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13791// Induces an intentional crash in the browser process.
792- (void)induceBrowserCrash;
793// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17794- (void)managePermissionAndSaveImage:(NSData*)data
795 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13796// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24797// saved as a temporary file on disk then saved in photos. Saving will happen
798// on a background sequence and the completion block will be invoked on that
799// sequence.
800- (void)saveImage:(NSData*)data
801 withFileExtension:(NSString*)fileExtension
802 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13803// Called when Chrome has been denied access to the photos or videos and the
804// user can change it.
805// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
806// settings. Dismiss previous alert if it has not been dismissed yet.
807- (void)displayImageErrorAlertWithSettingsOnMainQueue;
808// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
809// previous alert if it has not been dismissed yet.
810- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
811// Called when Chrome has been denied access to the photos or videos and the
812// user cannot change it.
813// Shows a privacy alert on the main queue, with errorContent as the message.
814// Dismisses previous alert if it has not been dismissed yet.
815- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
816// Called with the results of saving a picture in the photo album. If error is
817// nil the save succeeded.
818- (void)finishSavingImageWithError:(NSError*)error;
819// Provides a view that encompasses currently displayed infobar(s) or nil
820// if no infobar is presented.
821- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
822// Returns a vertical infobar offset relative to the tab content.
823- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
824// Provides a view that encompasses the voice search bar if it's displayed or
825// nil if the voice search bar isn't displayed.
826- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
827// Returns a vertical voice search bar offset relative to the tab content.
828- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
829// Lazily instantiates |_voiceSearchController|.
830- (void)ensureVoiceSearchControllerCreated;
831// Lazily instantiates |_voiceSearchBar| and adds it to the view.
832- (void)ensureVoiceSearchBarCreated;
833// Shows/hides the voice search bar.
834- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
835// The LogoAnimationControllerOwner to be used for the next logo transition
836// animation.
837- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13838// Returns the footer view if one exists (e.g. the voice search bar).
839- (UIView*)footerView;
840// Returns the height of the header view for the tab model's current tab.
841- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13842// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45843- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13844 atOffset:(CGFloat)headerOffset;
845// Returns the y coordinate for the footer's frame when animating the footer
846// in/out of fullscreen.
847- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
848// Called when the animation for setting the header view's offset is finished.
849// |completed| should indicate if the animation finished completely or was
850// interrupted. |offset| should indicate the header offset after the animation.
851// |dragged| should indicate if the header moved due to the user dragging.
852- (void)fullScreenController:(FullScreenController*)controller
853 headerAnimationCompleted:(BOOL)completed
854 offset:(CGFloat)offset;
855// Performs a search with the image at the given url. The referrer is used to
856// download the image.
857- (void)searchByImageAtURL:(const GURL&)url
858 referrer:(const web::Referrer)referrer;
859// Saves the image at the given URL on the system's album. The referrer is used
860// to download the image.
861- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
862
Mark Cogandfcdea72017-07-18 13:47:38863// Record the last tap point based on the |originPoint| (if any) passed in
864// |command|.
865- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13866// Get return the last stored |_lastTapPoint| if it's been set within the past
867// second.
868- (CGPoint)lastTapPoint;
869// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
870- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
871// Returns the native controller being used by |tab|'s web controller.
872- (id)nativeControllerForTab:(Tab*)tab;
873// Installs the BVC as overscroll actions controller of |nativeContent| if
874// needed. Sets the style of the overscroll actions toolbar.
875- (void)setOverScrollActionControllerToStaticNativeContent:
876 (StaticHtmlNativeContent*)nativeContent;
877// Whether the BVC should declare keyboard commands.
878- (BOOL)shouldRegisterKeyboardCommands;
879// Adds the given url to the reading list.
880- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
881@end
882
883class InfoBarContainerDelegateIOS
884 : public infobars::InfoBarContainer::Delegate {
885 public:
886 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
887 : controller_(controller) {}
888
889 ~InfoBarContainerDelegateIOS() override {}
890
891 private:
892 SkColor GetInfoBarSeparatorColor() const override {
893 NOTIMPLEMENTED();
894 return SK_ColorBLACK;
895 }
896
897 int ArrowTargetHeightForInfoBar(
898 size_t index,
899 const gfx::SlideAnimation& animation) const override {
900 return 0;
901 }
902
903 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
904 int arrow_target_height,
905 int bar_target_height,
906 int* arrow_height,
907 int* arrow_half_width,
908 int* bar_height) const override {
909 DCHECK_NE(-1, bar_target_height)
910 << "Infobars don't have a default height on iOS";
911 *arrow_height = 0;
912 *arrow_half_width = 0;
913 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
914 }
915
916 void InfoBarContainerStateChanged(bool is_animating) override {
917 [controller_ infoBarContainerStateChanged:is_animating];
918 }
919
920 bool DrawInfoBarArrows(int* x) const override { return false; }
921
stkhapuginf58b10d02017-04-10 13:36:17922 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13923};
924
925// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
926@interface BrowserViewController (BookmarkBridgeMethods)
927// If a bookmark matching the currentTab url is added or moved, update the
928// toolbar state so the star highlight is in sync.
929- (void)bookmarkNodeModified:(const BookmarkNode*)node;
930- (void)allBookmarksRemoved;
931@end
932
933// Handle notification that bookmarks has been removed changed so we can update
934// the bookmarked star icon.
935class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
936 public:
937 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
938 : owner_(owner) {}
939
940 ~BrowserBookmarkModelBridge() override {}
941
942 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
943 const BookmarkNode* parent,
944 int old_index,
945 const BookmarkNode* node,
946 const std::set<GURL>& removed_urls) override {
947 [owner_ bookmarkNodeModified:node];
948 }
949
950 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
951 bool ids_reassigned) override {}
952
953 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
954 const BookmarkNode* old_parent,
955 int old_index,
956 const BookmarkNode* new_parent,
957 int new_index) override {}
958
959 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
960 const BookmarkNode* parent,
961 int index) override {
962 [owner_ bookmarkNodeModified:parent->GetChild(index)];
963 }
964
965 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
966 const BookmarkNode* node) override {}
967
968 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
969 const BookmarkNode* node) override {}
970
971 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
972 const BookmarkNode* node) override {}
973
974 void BookmarkAllUserNodesRemoved(
975 bookmarks::BookmarkModel* model,
976 const std::set<GURL>& removed_urls) override {
977 [owner_ allBookmarksRemoved];
978 }
979
980 private:
stkhapuginf58b10d02017-04-10 13:36:17981 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13982};
983
984@implementation BrowserViewController
985
986@synthesize contentArea = _contentArea;
987@synthesize typingShield = _typingShield;
988@synthesize active = _active;
989@synthesize visible = _visible;
990@synthesize viewVisible = _viewVisible;
991@synthesize dismissingModal = _dismissingModal;
992@synthesize hideStatusBar = _hideStatusBar;
993@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
994@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39995@synthesize foregroundTabWasAddedCompletionBlock =
996 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40997@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
998@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42999@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:031000@synthesize tabStripCoordinator = _tabStripCoordinator;
1001@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:131002
1003#pragma mark - Object lifecycle
1004
Mark Cogan5e3da152017-07-11 15:57:301005- (instancetype)
1006 initWithTabModel:(TabModel*)model
1007 browserState:(ios::ChromeBrowserState*)browserState
1008 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
1009applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:131010 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
1011 if (self) {
1012 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:171013
stkhapuginc9eee7b2017-04-10 15:49:271014 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:271015 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
1016 presentingViewController:self];
justincohen75011c32017-04-28 16:31:391017 _dispatcher = [[CommandDispatcher alloc] init];
1018 [_dispatcher startDispatchingToTarget:self
1019 forProtocol:@protocol(UrlLoader)];
1020 [_dispatcher startDispatchingToTarget:self
1021 forProtocol:@protocol(WebToolbarDelegate)];
1022 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241023 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301024 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1025 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321026 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1027 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1028 // dispatched to the endpoint as well. Since this is potentially
1029 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1030 DCHECK(!applicationCommandEndpoint ||
1031 [applicationCommandEndpoint
1032 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1033 [_dispatcher
1034 startDispatchingToTarget:applicationCommandEndpoint
1035 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391036
edchin7f210cd2017-09-28 08:03:531037 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
1038 _snackbarCoordinator.dispatcher = _dispatcher;
1039 [_snackbarCoordinator start];
1040
sdefresnee65fd872016-12-19 13:38:131041 _javaScriptDialogPresenter.reset(
1042 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1043 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1044 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151045 [[UpgradeCenter sharedInstance] registerClient:self
1046 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131047 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131048 if (model && browserState)
1049 [self updateWithTabModel:model browserState:browserState];
1050 if ([[NSUserDefaults standardUserDefaults]
1051 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271052 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131053 }
1054 }
1055 return self;
1056}
1057
1058- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1059 bundle:(NSBundle*)nibBundleOrNil {
1060 NOTREACHED();
1061 return nil;
1062}
1063
1064- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1065 NOTREACHED();
1066 return nil;
1067}
1068
1069- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201070 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131071}
1072
1073#pragma mark - Accessibility
1074
1075- (BOOL)accessibilityPerformEscape {
1076 [self dismissPopups];
1077 return YES;
1078}
1079
1080#pragma mark - Properties
1081
edchin3365c7d2017-09-01 22:20:371082- (id<ApplicationCommands,
1083 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371084 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531085 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371086 UrlLoader,
1087 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561088 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531089 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1090 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241091}
1092
sdefresnee65fd872016-12-19 13:38:131093- (void)setActive:(BOOL)active {
1094 if (_active == active) {
1095 return;
1096 }
1097 _active = active;
1098
1099 // If not active, display an activity indicator overlay over the view to
1100 // prevent interaction with the web page.
1101 // TODO(crbug.com/637093): This coordinator should be managed by the
1102 // coordinator used to present BrowserViewController, when implemented.
1103 if (active) {
1104 [self.activityOverlayCoordinator stop];
1105 self.activityOverlayCoordinator = nil;
1106 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171107 self.activityOverlayCoordinator =
1108 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131109 [self.activityOverlayCoordinator start];
1110 }
1111
1112 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091113 ActiveStateManager* active_state_manager =
1114 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131115 active_state_manager->SetActive(active);
1116 }
1117
1118 [_model setWebUsageEnabled:active];
1119 [self updateDialogPresenterActiveState];
1120
1121 if (active) {
1122 // Make sure the tab (if any; it's possible to get here without a current
1123 // tab if the caller is about to create one) ends up on screen completely.
1124 Tab* currentTab = [_model currentTab];
1125 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441126 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131127 if (_expectingForegroundTab)
1128 [currentTab.webController setOverlayPreviewMode:YES];
1129 if (currentTab)
1130 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341131 } else {
1132 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131133 }
sdefresnee65fd872016-12-19 13:38:131134 [_paymentRequestManager enablePaymentRequest:active];
1135
1136 [self setNeedsStatusBarAppearanceUpdate];
1137}
1138
1139- (void)setPrimary:(BOOL)primary {
1140 [_model setPrimary:primary];
1141 if (primary) {
1142 [self updateDialogPresenterActiveState];
1143 } else {
1144 self.dialogPresenter.active = false;
1145 }
1146}
1147
1148- (BOOL)isPlayingTTS {
1149 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1150}
1151
sdefresne6165c8742017-01-16 15:42:021152- (ios::ChromeBrowserState*)browserState {
1153 return _browserState;
1154}
1155
1156- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271157 return _model;
sdefresne6165c8742017-01-16 15:42:021158}
1159
sdefresnee65fd872016-12-19 13:38:131160- (SideSwipeController*)sideSwipeController {
1161 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271162 _sideSwipeController =
1163 [[SideSwipeController alloc] initWithTabModel:_model
1164 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131165 [_sideSwipeController setSnapshotDelegate:self];
1166 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031167 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131168 }
1169 return _sideSwipeController;
1170}
1171
sdefresnee65fd872016-12-19 13:38:131172- (DialogPresenter*)dialogPresenter {
1173 return _dialogPresenter;
1174}
1175
sdefresnee65fd872016-12-19 13:38:131176- (BOOL)canUseDesktopUserAgent {
1177 Tab* tab = [_model currentTab];
1178 if ([self isTabNativePage:tab])
1179 return NO;
1180
1181 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441182 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131183}
1184
1185// Whether the sharing menu should be shown.
1186- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191187 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251188 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131189}
1190
1191- (BOOL)canShowFindBar {
1192 // Make sure web controller can handle find in page.
1193 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421194 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131195 return NO;
rohitrao005a6432017-03-16 20:52:421196 }
sdefresnee65fd872016-12-19 13:38:131197
rohitrao005a6432017-03-16 20:52:421198 auto* helper = FindTabHelper::FromWebState(tab.webState);
1199 return (helper && helper->CurrentPageSupportsFindInPage() &&
1200 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131201}
1202
liaoyukeea9f3ee62017-03-07 22:05:391203- (web::UserAgentType)userAgentType {
1204 web::WebState* webState = [_model currentTab].webState;
1205 if (!webState)
1206 return web::UserAgentType::NONE;
1207 web::NavigationItem* visibleItem =
1208 webState->GetNavigationManager()->GetVisibleItem();
1209 if (!visibleItem)
1210 return web::UserAgentType::NONE;
1211
1212 return visibleItem->GetUserAgentType();
1213}
1214
sdefresnee65fd872016-12-19 13:38:131215- (void)setVisible:(BOOL)visible {
1216 if (_visible == visible)
1217 return;
1218 _visible = visible;
1219}
1220
1221- (void)setViewVisible:(BOOL)viewVisible {
1222 if (_viewVisible == viewVisible)
1223 return;
1224 _viewVisible = viewVisible;
1225 self.visible = viewVisible;
1226 [self updateDialogPresenterActiveState];
1227}
1228
1229- (BOOL)isToolbarOnScreen {
1230 return [self headerHeight] - [self currentHeaderOffset] > 0;
1231}
1232
kkhorimotoa44349c12017-04-12 23:02:121233- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1234 if (_inNewTabAnimation == inNewTabAnimation)
1235 return;
1236 _inNewTabAnimation = inNewTabAnimation;
1237 [self updateDialogPresenterActiveState];
1238}
1239
sdefresnee65fd872016-12-19 13:38:131240- (BOOL)isInNewTabAnimation {
1241 return _inNewTabAnimation;
1242}
1243
1244- (BOOL)shouldShowVoiceSearchBar {
1245 // On iPads, the voice search bar should only be shown for regular horizontal
1246 // size class configurations. It should always be shown for voice search
1247 // results Tabs on iPhones, including configurations with regular horizontal
1248 // size classes (i.e. landscape iPhone 6 Plus).
1249 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1250 UIUserInterfaceSizeClassCompact;
1251 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1252 (!IsIPadIdiom() || compactWidth);
1253}
1254
1255- (void)setHideStatusBar:(BOOL)hideStatusBar {
1256 if (_hideStatusBar == hideStatusBar)
1257 return;
1258 _hideStatusBar = hideStatusBar;
1259 [self setNeedsStatusBarAppearanceUpdate];
1260}
1261
1262#pragma mark - IBActions
1263
1264- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461265 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131266}
1267
Cooper Knaakd0a974cd2017-08-10 18:05:471268- (void)userEnteredTabSwitcher {
1269 if ([self.tabTipBubblePresenter isUserEngaged]) {
1270 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1271 }
1272}
1273
Cooper Knaake963d6702017-08-11 21:03:111274- (void)presentBubblesIfEligible {
1275 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401276 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111277}
1278
sdefresnee65fd872016-12-19 13:38:131279#pragma mark - UIViewController methods
1280
1281// Perform additional set up after loading the view, typically from a nib.
1282- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091283 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281284 initialViewsRect.origin.y += StatusBarHeight();
1285 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131286 UIViewAutoresizing initialViewAutoresizing =
1287 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1288
stkhapuginf58b10d02017-04-10 13:36:171289 self.contentArea =
1290 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131291 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171292 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131293 self.typingShield.autoresizingMask = initialViewAutoresizing;
1294 [self.typingShield addTarget:self
1295 action:@selector(shieldWasTapped:)
1296 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131297 self.view.autoresizingMask = initialViewAutoresizing;
1298 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1299 [self.view addSubview:self.contentArea];
1300 [self.view addSubview:self.typingShield];
1301 [super viewDidLoad];
1302
1303 // Install fake status bar for iPad iOS7
1304 [self installFakeStatusBar];
1305 [self buildToolbarAndTabStrip];
1306 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501307 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
1308 [self addConstraintsToToolbar];
1309 }
sdefresnee65fd872016-12-19 13:38:131310 // If the tab model and browser state are valid, finish initialization.
1311 if (_model && _browserState)
1312 [self addUIFunctionalityForModelAndBrowserState];
1313
1314 // Add a tap gesture recognizer to save the last tap location for the source
1315 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271316 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1317 initWithTarget:self
1318 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131319 [tapRecognizer setDelegate:self];
1320 [tapRecognizer setCancelsTouchesInView:NO];
1321 [_contentArea addGestureRecognizer:tapRecognizer];
1322}
1323
Justin Cohenb3170c32017-09-19 01:55:221324- (void)viewSafeAreaInsetsDidChange {
1325 [super viewSafeAreaInsetsDidChange];
1326 // Gate this behind iPhone X, since it's currently the only device that
1327 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501328 if (IsIPhoneX()) {
Justin Cohenb3170c32017-09-19 01:55:221329 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501330 }
1331 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
Gauthier Ambard100670f72017-10-27 09:54:271332 // TODO(crbug.com/778236): Check if this call can be removed once the
1333 // Toolbar is a contained ViewController.
1334 [_toolbarCoordinator.toolbarController viewSafeAreaInsetsDidChange];
1335 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501336 }
Justin Cohenb3170c32017-09-19 01:55:221337}
1338
sdefresnee65fd872016-12-19 13:38:131339- (void)viewDidAppear:(BOOL)animated {
1340 [super viewDidAppear:animated];
1341 self.viewVisible = YES;
1342 [self updateDialogPresenterActiveState];
Gregory Chatzinoff541b8642017-10-25 00:25:211343
1344 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1345 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1346 // check for |self.browserState| before calling the presenting the bubbles.
1347 if (self.browserState) {
1348 [self presentBubblesIfEligible];
1349 }
sdefresnee65fd872016-12-19 13:38:131350}
1351
1352- (void)viewWillAppear:(BOOL)animated {
1353 [super viewWillAppear:animated];
1354
1355 // Reparent the toolbar if it's been relinquished.
1356 if (_isToolbarControllerRelinquished)
1357 [self reparentToolbarController];
1358
1359 self.visible = YES;
1360
1361 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251362 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131363 _infoBarContainer->RestoreInfobars();
1364 }
1365
1366 // If the controller is suspended, or has been paged out due to low memory,
1367 // updating the view will be handled when it's displayed again.
1368 if (![_model webUsageEnabled] || !self.contentArea)
1369 return;
1370 // Update the displayed tab (if any; the switcher may not have created one
1371 // yet) in case it changed while showing the switcher.
1372 Tab* currentTab = [_model currentTab];
1373 if (currentTab)
1374 [self displayTab:currentTab isNewSelection:YES];
1375}
1376
1377- (void)viewWillDisappear:(BOOL)animated {
1378 self.viewVisible = NO;
1379 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131380 [[_model currentTab] wasHidden];
1381 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251382 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131383 _infoBarContainer->SuspendInfobars();
1384 }
1385 [super viewWillDisappear:animated];
1386}
1387
1388- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1389 duration:(NSTimeInterval)duration {
1390 [super willRotateToInterfaceOrientation:orient duration:duration];
1391 [self dismissPopups];
1392 [self reshowFindBarIfNeededWithCoordinator:nil];
1393}
1394
1395- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1396 [super didRotateFromInterfaceOrientation:orient];
1397
1398 // This reinitializes the toolbar, including updating the Overlay View,
1399 // if there is one.
1400 [self updateToolbar];
1401 [self infoBarContainerStateChanged:false];
1402}
1403
1404- (BOOL)prefersStatusBarHidden {
1405 return self.hideStatusBar;
1406}
1407
1408// Called when in the foreground and the OS needs more memory. Release as much
1409// as possible.
1410- (void)didReceiveMemoryWarning {
1411 // Releases the view if it doesn't have a superview.
1412 [super didReceiveMemoryWarning];
1413
1414 // Release any cached data, images, etc that aren't in use.
1415 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1416 // but since the FaviconCache uses obj-c in the header, it can't be included
1417 // there.
1418 if (_browserState) {
1419 FaviconLoader* loader =
1420 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1421 _browserState);
1422 if (loader)
1423 loader->PurgeCache();
1424 }
1425
1426 if (![self isViewLoaded]) {
1427 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1428 // as the BrowserViewController.
1429 self.contentArea = nil;
1430 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271431 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131432 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271433 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421434 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461435 _toolbarCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271436 _toolbarModelDelegate = nil;
1437 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031438 [self.tabStripCoordinator stop];
1439 self.tabStripCoordinator = nil;
1440 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271441 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131442 }
1443}
1444
1445- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1446 [super traitCollectionDidChange:previousTraitCollection];
1447 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1448 // because in some cases the presented view controller isn't a child of the
1449 // BVC in the view controller hierarchy (some intervening object isn't a
1450 // view controller).
1451 [self.presentedViewController
1452 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131453 // Update voice search bar visibility.
1454 [self updateVoiceSearchBarVisibilityAnimated:NO];
1455}
1456
1457- (void)viewWillTransitionToSize:(CGSize)size
1458 withTransitionCoordinator:
1459 (id<UIViewControllerTransitionCoordinator>)coordinator {
1460 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1461 [self dismissPopups];
1462 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1463}
1464
1465- (void)reshowFindBarIfNeededWithCoordinator:
1466 (id<UIViewControllerTransitionCoordinator>)coordinator {
1467 if (![_findBarController isFindInPageShown])
1468 return;
1469
1470 // Record focused state.
1471 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1472
1473 [self hideFindBarWithAnimation:NO];
1474
stkhapuginc9eee7b2017-04-10 15:49:271475 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131476 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1477 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271478 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131479 if (strongSelf)
1480 [strongSelf showFindBarWithAnimation:NO
1481 selectText:NO
1482 shouldFocus:isFocusedBeforeReshow];
1483 };
1484
1485 BOOL enqueued =
1486 [coordinator animateAlongsideTransition:nil completion:completion];
1487 if (!enqueued) {
1488 completion(nil);
1489 }
1490}
1491
1492- (void)dismissViewControllerAnimated:(BOOL)flag
1493 completion:(void (^)())completion {
1494 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271495 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131496 [super dismissViewControllerAnimated:flag
1497 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271498 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131499 [strongSelf setDismissingModal:NO];
1500 [strongSelf setPresenting:NO];
1501 if (completion)
1502 completion();
1503 [[strongSelf dialogPresenter] tryToPresent];
1504 }];
1505}
1506
1507- (void)presentViewController:(UIViewController*)viewControllerToPresent
1508 animated:(BOOL)flag
1509 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271510 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131511 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1512 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1513 // presented, to show a temporary view of the launch screen and then remove it
1514 // when the controller for the FRE has been presented. This fix should be
1515 // removed when the FRE startup code is rewritten.
1516 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1517 experimental_flags::AlwaysDisplayFirstRun()) &&
1518 !tests_hook::DisableFirstRun();
1519 // These if statements check that |presentViewController| is being called for
1520 // the FRE case.
1521 if (firstRunLaunch &&
1522 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1523 UINavigationController* navController =
1524 base::mac::ObjCCastStrict<UINavigationController>(
1525 viewControllerToPresent);
1526 if ([navController.topViewController
1527 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1528 self.hideStatusBar = YES;
1529
1530 // Load view from Launch Screen and add it to window.
1531 NSBundle* mainBundle = base::mac::FrameworkBundle();
1532 NSArray* topObjects =
1533 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1534 UIViewController* launchScreenController =
1535 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1536 // |launchScreenView| is loaded as an autoreleased object, and is retained
1537 // by the |completion| block below.
1538 UIView* launchScreenView = launchScreenController.view;
1539 launchScreenView.userInteractionEnabled = NO;
1540 launchScreenView.frame = self.view.window.bounds;
1541 [self.view.window addSubview:launchScreenView];
1542
1543 // Replace the completion handler sent to the superclass with one which
1544 // removes |launchScreenView| and resets the status bar. If |completion|
1545 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271546 __weak BrowserViewController* weakSelf = self;
1547 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131548 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271549 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131550 if (completion)
1551 completion();
stkhapuginc9eee7b2017-04-10 15:49:271552 };
sdefresnee65fd872016-12-19 13:38:131553 }
1554 }
1555
1556 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171557 if ([_sideSwipeController inSwipe]) {
1558 [_sideSwipeController resetContentView];
1559 }
sdefresnee65fd872016-12-19 13:38:131560
1561 [super presentViewController:viewControllerToPresent
1562 animated:flag
1563 completion:finalCompletionHandler];
1564}
1565
1566#pragma mark - Notification handling
1567
1568- (void)registerForNotifications {
1569 DCHECK(_model);
1570 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1571 [defaultCenter addObserver:self
1572 selector:@selector(pageLoadStarting:)
1573 name:kTabModelTabWillStartLoadingNotification
1574 object:_model];
1575 [defaultCenter addObserver:self
1576 selector:@selector(pageLoadStarted:)
1577 name:kTabModelTabDidStartLoadingNotification
1578 object:_model];
1579 [defaultCenter addObserver:self
1580 selector:@selector(pageLoadComplete:)
1581 name:kTabModelTabDidFinishLoadingNotification
1582 object:_model];
1583 [defaultCenter addObserver:self
1584 selector:@selector(tabDeselected:)
1585 name:kTabModelTabDeselectedNotification
1586 object:_model];
1587 [defaultCenter addObserver:self
1588 selector:@selector(tabWasAdded:)
1589 name:kTabModelNewTabWillOpenNotification
1590 object:_model];
1591}
1592
1593- (void)pageLoadStarting:(NSNotification*)notify {
1594 Tab* tab = notify.userInfo[kTabModelTabKey];
1595 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511596
1597 // Stop any Find in Page searches and close the find bar when navigating to a
1598 // new page.
1599 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361600
sdefresnee65fd872016-12-19 13:38:131601 if (tab == [_model currentTab]) {
1602 // TODO(pinkerton): Fill in here about hiding the forward button on
1603 // navigation.
1604 }
1605}
1606
1607- (void)pageLoadStarted:(NSNotification*)notify {
1608 Tab* tab = notify.userInfo[kTabModelTabKey];
1609 DCHECK(tab);
1610 if (tab == [_model currentTab]) {
1611 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461612 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131613 }
1614 [self updateVoiceSearchBarVisibilityAnimated:NO];
1615 }
1616}
1617
1618- (void)pageLoadComplete:(NSNotification*)notify {
1619 // Update the UI, but only if the current tab.
1620 Tab* tab = notify.userInfo[kTabModelTabKey];
1621 if (tab == [_model currentTab]) {
1622 // There isn't any need to update the toolbar here. When the page finishes,
1623 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1624 }
1625
1626 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1627
1628 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1629}
1630
1631- (void)tabDeselected:(NSNotification*)notify {
1632 DCHECK(notify);
1633 Tab* tab = notify.userInfo[kTabModelTabKey];
1634 DCHECK(tab);
1635 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221636 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131637}
1638
1639- (void)tabWasAdded:(NSNotification*)notify {
1640 Tab* tab = notify.userInfo[kTabModelTabKey];
1641 DCHECK(tab);
1642
Eugene But56efc322017-08-11 14:03:441643 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131644
1645 // When adding new tabs, check what kind of reminder infobar should
1646 // be added to the new tab. Try to add only one of them.
1647 // This check is done when a new tab is added either through the Tools Menu
1648 // "New Tab" or through "New Tab" in Stack View Controller. This method
1649 // is called after a new tab has added and finished initial navigation.
1650 // If this is added earlier, the initial navigation may end up clearing
1651 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301652 web::WebState* webState = tab.webState;
1653 DCHECK(webState);
1654
1655 infobars::InfoBarManager* infoBarManager =
1656 InfoBarManagerImpl::FromWebState(webState);
1657 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131658 forTabId:[tab tabId]];
edchinbb8ba892017-09-12 15:44:031659 if (!ReSignInInfoBarDelegate::Create(_browserState, tab, self.dispatcher)) {
edchin9cad67b2017-09-11 20:13:571660 DisplaySyncErrors(_browserState, tab, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:131661 }
1662
1663 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501664 // phone-specific. Call the foreground tab added completion block; for
1665 // iPhones, this will get executed after the animation has finished.
1666 if (IsIPadIdiom()) {
1667 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431668 // This callback is called before webState is activated (on
1669 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1670 // callback asynchronously to be sure the activation is complete.
1671 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011672 // Test existence again as the block may have been deleted.
1673 if (self.foregroundTabWasAddedCompletionBlock) {
1674 self.foregroundTabWasAddedCompletionBlock();
1675 self.foregroundTabWasAddedCompletionBlock = nil;
1676 }
Olivier Robinc7e46242017-09-06 07:55:431677 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501678 }
sdefresnee65fd872016-12-19 13:38:131679 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501680 }
sdefresnee65fd872016-12-19 13:38:131681
1682 // Do nothing if browsing is currently suspended. The BVC will set everything
1683 // up correctly when browsing resumes.
1684 if (!self.visible || ![_model webUsageEnabled])
1685 return;
1686
1687 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1688
1689 // Block that starts voice search at the end of new Tab animation if
1690 // necessary.
1691 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1692 if (_startVoiceSearchAfterNewTabAnimation) {
1693 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001694 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131695 }
1696 };
1697
kkhorimotoa44349c12017-04-12 23:02:121698 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131699 if (!inBackground) {
1700 UIView* animationParentView = _contentArea;
1701 // Create the new page image, and load with the new tab page snapshot.
1702 CGFloat newPageOffset = 0;
1703 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191704 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1705 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131706 animationParentView = self.view;
1707 newPage = [self pageFullScreenOpenCloseAnimationView];
1708 } else {
1709 newPage = [self pageOpenCloseAnimationView];
1710 }
1711 newPageOffset = newPage.frame.origin.y;
1712
1713 [tab view].frame = _contentArea.bounds;
1714 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1715 [animationParentView addSubview:newPage];
1716 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521717 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131718 newPage, -newPageOffset,
1719 newPage.frame.size.height - newPage.image.size.height, origin,
1720 _isOffTheRecord, NULL, ^{
1721 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121722 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421723 // Use the model's currentTab here because it is possible that it can
1724 // be reset to a new value before the new Tab animation finished (e.g.
1725 // if another Tab shows a dialog via |dialogPresenter|). However, that
1726 // tab's view hasn't been displayed yet because it was in a new tab
1727 // animation.
1728 Tab* currentTab = [_model currentTab];
1729 if (currentTab) {
1730 [self tabSelected:currentTab];
1731 }
sdefresnee65fd872016-12-19 13:38:131732 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391733
1734 if (self.foregroundTabWasAddedCompletionBlock) {
1735 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451736 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391737 }
sdefresnee65fd872016-12-19 13:38:131738 });
1739 } else {
1740 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1741 // snapshot before adding the views needed for the background animation.
1742 Tab* topTab = [_model currentTab];
1743 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1744 visibleFrameOnly:self.isToolbarOnScreen];
1745 // Add three layers in order on top of the contentArea for the animation:
1746 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271747 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131748 InstallBackgroundInView(background);
1749 [_contentArea addSubview:background];
1750
1751 // 2. A CardView displaying the data from the current tab.
1752 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1753 NSString* title = [topTab title];
1754 if (![title length])
1755 title = [topTab urlDisplayString];
1756 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131757 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371758 [topCard setFavicon:nil];
1759
1760 favicon::FaviconDriver* faviconDriver =
1761 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1762 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1763 gfx::Image favicon = faviconDriver->GetFavicon();
1764 if (!favicon.IsEmpty())
1765 [topCard setFavicon:favicon.ToUIImage()];
1766 }
sdefresnee65fd872016-12-19 13:38:131767
1768 // 3. A new, blank CardView to represent the new tab being added.
1769 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521770 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131771 topCard, [_contentArea frame], IsPortrait(), ^{
1772 [background removeFromSuperview];
1773 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121774 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131775 // Resnapshot the top card if it has its own toolbar, as the toolbar
1776 // will be captured in the new tab animation, but isn't desired for
1777 // the stack view snapshots.
1778 id nativeController = [self nativeControllerForTab:topTab];
1779 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1780 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1781 startVoiceSearchIfNecessaryBlock();
1782 });
peterlaurens9f1b6e02017-06-22 17:46:451783 // Reset the foreground tab completion block so that it can never be
1784 // called more than once regardless of foreground/background tab
1785 // appearances.
1786 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131787 }
1788}
1789
1790#pragma mark - UI Configuration and Layout
1791
1792- (void)updateWithTabModel:(TabModel*)model
1793 browserState:(ios::ChromeBrowserState*)browserState {
1794 DCHECK(model);
1795 DCHECK(browserState);
1796 DCHECK(!_model);
1797 DCHECK(!_browserState);
1798 _browserState = browserState;
1799 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271800 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381801
sdefresnee65fd872016-12-19 13:38:131802 [_model addObserver:self];
1803
1804 if (!_isOffTheRecord) {
1805 [DefaultIOSWebViewFactory
1806 registerWebViewFactory:[ChromeWebViewFactory class]];
1807 }
1808 NSUInteger count = [_model count];
1809 for (NSUInteger index = 0; index < count; ++index)
1810 [self installDelegatesForTab:[_model tabAtIndex:index]];
1811
1812 [self registerForNotifications];
1813
gambardbdc07cc2017-02-03 16:43:111814 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121815 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271816 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131817
sdefresnedc432f42017-01-17 14:36:591818 // Register for bookmark changed notification (BookmarkModel may be null
1819 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131820 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591821 if (_bookmarkModel) {
1822 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1823 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1824 }
sdefresnee65fd872016-12-19 13:38:131825}
1826
sdefresnee65fd872016-12-19 13:38:131827- (void)browserStateDestroyed {
1828 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131829 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271830 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461831 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131832 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381833
1834 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431835 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111836 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381837 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271838 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351839 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031840 [self.tabStripCoordinator stop];
1841 self.tabStripCoordinator = nil;
1842 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381843
sdefresnee65fd872016-12-19 13:38:131844 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391845 [_dispatcher stopDispatchingToTarget:self];
1846 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131847}
1848
1849- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221850 CGFloat statusBarHeight = StatusBarHeight();
1851 CGRect statusBarFrame =
1852 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1853 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1854 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131855 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221856 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1857 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1858 [_fakeStatusBarView layer].zPosition = 99;
1859 [[self view] addSubview:_fakeStatusBarView];
1860 } else {
1861 // Add a white bar on phone so that the status bar on the NTP is white.
1862 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1863 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131864 }
1865}
1866
1867// Create the UI elements. May or may not have valid browser state & tab model.
1868- (void)buildToolbarAndTabStrip {
1869 DCHECK([self isViewLoaded]);
1870 DCHECK(!_toolbarModelDelegate);
1871
Rohit Rao44f204302017-08-10 14:49:541872 // Initialize the prerender service before creating the toolbar controller.
1873 PrerenderService* prerenderService =
1874 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1875 if (prerenderService) {
1876 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131877 }
1878
1879 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201880 _toolbarModelDelegate.reset(
1881 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131882 _toolbarModelIOS.reset([_dependencyFactory
1883 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
sczsf1620e52017-10-02 22:54:461884 _toolbarCoordinator =
1885 [[LegacyToolbarCoordinator alloc] initWithBaseViewController:self];
1886 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051887 [_toolbarCoordinator
1888 setWebToolbar:[_dependencyFactory
1889 newWebToolbarControllerWithDelegate:self
1890 urlLoader:self
1891 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461892 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391893 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461894 [_toolbarCoordinator setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271895 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461896 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051897 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131898
sdefresnee65fd872016-12-19 13:38:131899 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031900 self.tabStripCoordinator =
1901 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1902 self.tabStripCoordinator.browserState = _browserState;
1903 self.tabStripCoordinator.dispatcher = _dispatcher;
1904 self.tabStripCoordinator.tabModel = _model;
1905 self.tabStripCoordinator.presentationProvider = self;
1906 self.tabStripCoordinator.animationWaitDuration =
1907 kFullScreenControllerToolbarAnimationDuration;
1908 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131909 }
1910
1911 // Create infobar container.
1912 if (!_infoBarContainerDelegate) {
1913 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1914 _infoBarContainer.reset(
1915 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1916 }
1917}
1918
Jean-François Geyelined4cde72017-10-11 11:34:501919- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111920 NSLayoutYAxisAnchor* topAnchor;
1921 // On iPad, the toolbar is underneath the tab strip.
1922 // On iPhone, it is underneath the top of the screen.
1923 if (IsIPadIdiom()) {
1924 topAnchor = self.tabStripView.bottomAnchor;
1925 } else {
1926 topAnchor = [self view].topAnchor;
1927 }
1928
Gauthier Ambard100670f72017-10-27 09:54:271929 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501930
1931 [NSLayoutConstraint activateConstraints:@[
1932 [[_toolbarCoordinator view].leadingAnchor
1933 constraintEqualToAnchor:[self view].leadingAnchor],
Jean-François Geyelince0a4742017-10-25 12:34:111934 [[_toolbarCoordinator view].topAnchor constraintEqualToAnchor:topAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501935 [[_toolbarCoordinator view].trailingAnchor
1936 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501937 ]];
1938 [[self view] layoutIfNeeded];
1939}
1940
sdefresnee65fd872016-12-19 13:38:131941// Enable functionality that only makes sense if the views are loaded and
1942// both browser state and tab model are valid.
1943- (void)addUIFunctionalityForModelAndBrowserState {
1944 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051945 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131946 DCHECK(_model);
1947 DCHECK([self isViewLoaded]);
1948
1949 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1950
Rohit Raoaf46af92017-08-10 12:52:301951 infobars::InfoBarManager* infoBarManager = nullptr;
1952 if (_model.currentTab) {
1953 DCHECK(_model.currentTab.webState);
1954 infoBarManager =
1955 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1956 }
sdefresnee65fd872016-12-19 13:38:131957 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1958
sczsdd860eba2017-08-10 01:55:381959 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431960 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1961 initWithBaseViewController:self];
1962 _activityServiceCoordinator.dispatcher = _dispatcher;
1963 _activityServiceCoordinator.tabModel = _model;
1964 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461965 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051966 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:431967 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:431968
Rohit Raocda0a992017-08-16 15:37:111969 _qrScannerCoordinator =
1970 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1971 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:051972 _qrScannerCoordinator.loadProvider =
1973 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:111974 _qrScannerCoordinator.presentationProvider = self;
1975
sczsdd860eba2017-08-10 01:55:381976 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131977 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381978 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:461979 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051980 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:381981 _tabHistoryCoordinator.tabModel = _model;
1982 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:461983 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:051984 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:381985
sczs6ae47ad2017-09-06 17:26:531986 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:201987 _sadTabCoordinator.baseViewController = self;
1988 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:531989
Gregory Chatzinoffdf93d692017-09-09 01:32:271990 _pageInfoCoordinator =
1991 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
1992 _pageInfoCoordinator.browserState = _browserState;
1993 _pageInfoCoordinator.dispatcher = _dispatcher;
1994 _pageInfoCoordinator.loader = self;
1995 _pageInfoCoordinator.presentationProvider = self;
1996 _pageInfoCoordinator.tabModel = _model;
1997
Louis Romerod11747a2017-10-20 20:10:351998 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
1999 _externalSearchCoordinator.dispatcher = _dispatcher;
2000
mathp9b4c11d2017-07-06 20:24:132001 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272002 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132003 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202004 browserState:_browserState
2005 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052006 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192007 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132008 }
2009}
2010
2011// Set the frame for the various views. View must be loaded.
2012- (void)setUpViewLayout {
2013 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132014 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132015 CGFloat minY = [self headerOffset];
2016
Justin Cohenb3170c32017-09-19 01:55:222017 // Update the fake toolbar background height.
2018 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2019 fakeStatusBarFrame.size.height = StatusBarHeight();
2020 _fakeStatusBarView.frame = fakeStatusBarFrame;
2021
edchinf5150c682017-09-18 02:50:032022 if (self.tabStripView) {
2023 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132024 }
2025
2026 // Position the toolbar next, either at the top of the browser view or
2027 // directly under the tabstrip.
sczsf1620e52017-10-02 22:54:462028 CGRect toolbarFrame = [[_toolbarCoordinator view] frame];
sdefresnee65fd872016-12-19 13:38:132029 toolbarFrame.origin = CGPointMake(0, minY);
2030 toolbarFrame.size.width = widthOfView;
Jean-François Geyelined4cde72017-10-11 11:34:502031 if (!base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
2032 [[_toolbarCoordinator view] setFrame:toolbarFrame];
2033 }
sdefresnee65fd872016-12-19 13:38:132034
2035 // Place the infobar container above the content area.
2036 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
2037 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
2038
2039 // Place the toolbar controller above the infobar container.
sczsf1620e52017-10-02 22:54:462040 [[self view] insertSubview:[_toolbarCoordinator view]
sdefresnee65fd872016-12-19 13:38:132041 aboveSubview:infoBarContainerView];
2042 minY += CGRectGetHeight(toolbarFrame);
2043
2044 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2045 // content slightly.
sczs8c837782017-10-03 02:57:242046 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132047
2048 // Adjust the content area to be under the toolbar, for fullscreen or below
2049 // the toolbar is not fullscreen.
2050 CGRect contentFrame = [_contentArea frame];
2051 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222052 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2053 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132054 [_contentArea setFrame:contentFrame];
2055
2056 // Adjust the infobar container to be either at the bottom of the screen
2057 // (iPhone) or on the lower toolbar edge (iPad).
2058 CGRect infoBarFrame = contentFrame;
2059 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2060 infoBarFrame.size.height = 0;
2061 [infoBarContainerView setFrame:infoBarFrame];
2062
2063 // Attach the typing shield to the content area but have it hidden.
2064 [_typingShield setFrame:[_contentArea frame]];
2065 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
2066 [_typingShield setHidden:YES];
2067 _typingShield.accessibilityIdentifier = @"Typing Shield";
2068 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
2069}
2070
sdefresnee65fd872016-12-19 13:38:132071- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2072 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442073 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132074
kkhorimotoa44349c12017-04-12 23:02:122075 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132076 // Hide findbar. |updateToolbar| will restore the findbar later.
2077 [self hideFindBarWithAnimation:NO];
2078
2079 // Make new content visible, resizing it first as the orientation may
2080 // have changed from the last time it was displayed.
2081 [[tab view] setFrame:_contentArea.bounds];
2082 [_contentArea displayContentView:[tab view]];
2083 }
2084 [self updateToolbar];
2085
2086 if (newSelection)
sczsf1620e52017-10-02 22:54:462087 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132088
2089 // Notify the Tab that it was displayed.
2090 [tab wasShown];
2091}
2092
2093- (void)initializeBookmarkInteractionController {
2094 if (_bookmarkInteractionController)
2095 return;
edchinbb8ba892017-09-12 15:44:032096 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2097 initWithBrowserState:_browserState
2098 loader:self
2099 parentController:self
2100 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132101}
2102
2103// Update the state of back and forward buttons, hiding the forward button if
2104// there is nowhere to go. Assumes the model's current tab is up to date.
2105- (void)updateToolbar {
2106 // If the BVC has been partially torn down for low memory, wait for the
2107 // view rebuild to handle toolbar updates.
2108 if (!(_toolbarModelIOS && _browserState))
2109 return;
2110
2111 Tab* tab = [_model currentTab];
2112 if (![tab navigationManager])
2113 return;
sczsf1620e52017-10-02 22:54:462114 [_toolbarCoordinator updateToolbarState];
2115 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132116
Rohit Rao44f204302017-08-10 14:49:542117 PrerenderService* prerenderService =
2118 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2119 BOOL isPrerenderTab =
2120 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2121 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462122 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132123
2124 // Also update the loading state for the tools menu (that is really an
2125 // extension of the toolbar on the iPhone).
2126 if (!IsIPadIdiom())
sczsf1620e52017-10-02 22:54:462127 [[_toolbarCoordinator toolsPopupController]
sdefresnee65fd872016-12-19 13:38:132128 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2129
rohitrao005a6432017-03-16 20:52:422130 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2131 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132132 [self showFindBarWithAnimation:NO
2133 selectText:YES
2134 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362135 }
sdefresnee65fd872016-12-19 13:38:132136
2137 // Hide the toolbar if displaying phone NTP.
2138 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552139 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132140 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552141 if (item) {
2142 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132143 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2144 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462145 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2146 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132147 }
sczsf1620e52017-10-02 22:54:462148 [[_toolbarCoordinator view] setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132149 }
2150}
2151
2152- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122153 self.dialogPresenter.active =
2154 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132155}
2156
2157- (void)dismissPopups {
sczsf1620e52017-10-02 22:54:462158 [_toolbarCoordinator dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272159 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282160 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472161 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382162}
2163
Cooper Knaakd0a974cd2017-08-10 18:05:472164- (BubbleViewControllerPresenter*)
2165bubblePresenterForFeature:(const base::Feature&)feature
2166 direction:(BubbleArrowDirection)direction
2167 alignment:(BubbleAlignment)alignment
2168 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212169 DCHECK(self.browserState);
2170 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382171 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472172 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382173 }
2174 // Capture |weakSelf| instead of the feature engagement tracker object
2175 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2176 // the feature engagement tracker will remain pointing to invalid memory if
2177 // its owner (the ChromeBrowserState) is deallocated.
2178 __weak BrowserViewController* weakSelf = self;
2179 void (^dismissalCallback)(void) = ^() {
2180 BrowserViewController* strongSelf = weakSelf;
2181 if (strongSelf) {
2182 feature_engagement::TrackerFactory::GetForBrowserState(
2183 strongSelf.browserState)
2184 ->Dismissed(feature);
2185 }
2186 };
2187
Cooper Knaakd0a974cd2017-08-10 18:05:472188 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382189 [[BubbleViewControllerPresenter alloc] initWithText:text
2190 arrowDirection:direction
2191 alignment:alignment
2192 dismissalCallback:dismissalCallback];
2193
Cooper Knaakd0a974cd2017-08-10 18:05:472194 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132195}
2196
Cooper Knaak120cee5e2017-08-10 20:57:002197- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212198 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002199 // If the tab tip bubble has already been presented and the user is still
2200 // considered engaged, it can't be overwritten or set to |nil| or else it will
2201 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2202 // can be safely overwritten or set to |nil|.
2203 if (!self.tabTipBubblePresenter.isUserEngaged) {
2204 __weak BrowserViewController* weakSelf = self;
2205 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2206 [weakSelf presentNewTabTipBubble];
2207 };
2208
2209 // Because the new tab tip occurs on startup, the feature engagement
2210 // tracker's database is not guaranteed to be loaded by this time. For the
2211 // bubble to appear properly, a callback is used to guarantee the event data
2212 // is loaded before the check to see if the promotion should be displayed.
2213 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2214 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2215 }
2216}
2217
2218- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212219 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002220 NSString* text =
2221 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2222 CGPoint tabSwitcherAnchor;
2223 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032224 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002225 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032226 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002227 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2228 } else {
sczsf1620e52017-10-02 22:54:462229 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002230 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462231 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002232 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2233 }
Cooper Knaake963d6702017-08-11 21:03:112234 // If the feature engagement tracker does not consider it valid to display
2235 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2236 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002237 self.tabTipBubblePresenter =
2238 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2239 direction:BubbleArrowDirectionUp
2240 alignment:BubbleAlignmentTrailing
2241 text:text];
2242 [self.tabTipBubblePresenter presentInViewController:self
2243 view:self.view
2244 anchorPoint:tabSwitcherAnchor];
2245}
2246
Helen Yang9175bd52017-08-12 00:28:402247- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212248 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402249 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2250 // user is still considered engaged.
2251 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2252 __weak BrowserViewController* weakSelf = self;
2253 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2254 [weakSelf presentNewIncognitoTabTipBubble];
2255 };
2256
2257 // Use a callback in case the new incognito tab tip should be shown on
2258 // startup. This ensures that the tracker's database will be fully loaded
2259 // before checking if the promotion should be displayed.
2260 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2261 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2262 }
2263}
2264
2265- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212266 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462267 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402268 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2269 NSString* text = l10n_util::GetNSStringWithFixup(
2270 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462271 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402272 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2273 self.incognitoTabTipBubblePresenter =
2274 [self bubblePresenterForFeature:feature_engagement::
2275 kIPHNewIncognitoTabTipFeature
2276 direction:BubbleArrowDirectionUp
2277 alignment:BubbleAlignmentTrailing
2278 text:text];
2279 [self.incognitoTabTipBubblePresenter
2280 presentInViewController:self
2281 view:self.view
2282 anchorPoint:toolsButtonAnchor];
2283 // Only trigger the tools menu button animation if the bubble is shown.
2284 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462285 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402286 }
2287}
2288
sdefresnee65fd872016-12-19 13:38:132289#pragma mark - Tap handling
2290
Mark Cogandfcdea72017-07-18 13:47:382291- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032292 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2293 _lastTapPoint = CGPointZero;
2294 } else {
2295 _lastTapPoint =
2296 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132297 }
Mark Cogane01ebce2017-07-12 19:31:032298 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132299}
2300
2301- (CGPoint)lastTapPoint {
2302 if (CACurrentMediaTime() - _lastTapTime < 1) {
2303 return _lastTapPoint;
2304 }
2305 return CGPointZero;
2306}
2307
2308- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2309 UIView* view = gestureRecognizer.view;
2310 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2311 _lastTapPoint =
2312 [[view superview] convertPoint:viewCoordinate toView:self.view];
2313 _lastTapTime = CACurrentMediaTime();
2314}
2315
2316- (BOOL)addTabIfNoTabWithNormalBrowserState {
2317 if (![_model count]) {
2318 if (!_isOffTheRecord) {
2319 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2320 transition:ui::PAGE_TRANSITION_TYPED];
2321 return YES;
2322 }
2323 }
2324 return NO;
2325}
2326
2327#pragma mark - Tab creation and selection
2328
2329// Called when either a tab finishes loading or when a tab with finished content
2330// is added directly to the model via pre-rendering.
2331- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2332 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2333
2334 // Persist the session on a delay.
2335 [_model saveSessionImmediately:NO];
2336}
2337
2338- (Tab*)addSelectedTabWithURL:(const GURL&)url
2339 postData:(TemplateURLRef::PostContent*)postData
2340 transition:(ui::PageTransition)transition {
2341 return [self addSelectedTabWithURL:url
2342 postData:postData
2343 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492344 transition:transition
2345 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132346}
2347
2348- (Tab*)addSelectedTabWithURL:(const GURL&)url
2349 transition:(ui::PageTransition)transition {
2350 return [self addSelectedTabWithURL:url
2351 atIndex:[_model count]
2352 transition:transition];
2353}
2354
2355- (Tab*)addSelectedTabWithURL:(const GURL&)url
2356 atIndex:(NSUInteger)position
2357 transition:(ui::PageTransition)transition {
2358 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492359 atIndex:position
2360 transition:transition
2361 tabAddedCompletion:nil];
2362}
2363
2364- (Tab*)addSelectedTabWithURL:(const GURL&)url
2365 atIndex:(NSUInteger)position
2366 transition:(ui::PageTransition)transition
2367 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2368 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132369 postData:NULL
2370 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492371 transition:transition
2372 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132373}
2374
2375- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2376 postData:(TemplateURLRef::PostContent*)postData
2377 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492378 transition:(ui::PageTransition)transition
2379 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132380 if (position == NSNotFound)
2381 position = [_model count];
2382 DCHECK(position <= [_model count]);
2383
2384 web::NavigationManager::WebLoadParams params(URL);
2385 params.transition_type = transition;
2386 if (postData) {
2387 // Extract the content type and post params from |postData| and add them
2388 // to the load params.
2389 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2390 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2391 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172392 params.post_data.reset(data);
2393 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132394 }
Olivier Robind508a5632017-07-19 16:29:492395
2396 if (tabAddedCompletion) {
2397 if (self.foregroundTabWasAddedCompletionBlock) {
2398 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2399 self.foregroundTabWasAddedCompletionBlock;
2400 self.foregroundTabWasAddedCompletionBlock = ^{
2401 oldForegroundTabWasAddedCompletionBlock();
2402 tabAddedCompletion();
2403 };
2404 } else {
2405 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2406 }
2407 }
2408
sdefresnea6395912017-03-01 01:14:352409 Tab* tab = [_model insertTabWithLoadParams:params
2410 opener:nil
2411 openedByDOM:NO
2412 atIndex:position
2413 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132414 return tab;
2415}
2416
olivierrobin889af53f2017-03-01 14:56:322417// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132418- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322419 web::WebState* webState = tab.webState;
2420 if (!webState)
2421 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392422 web::NavigationItem* visibleItem =
2423 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322424 if (!visibleItem)
2425 return NO;
2426 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132427}
2428
2429- (void)expectNewForegroundTab {
2430 _expectingForegroundTab = YES;
2431}
2432
2433- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2434 CGRect viewBounds, remainder;
2435 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2436 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172437 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132438}
2439
2440- (UIImageView*)pageOpenCloseAnimationView {
2441 CGRect frame = [_contentArea bounds];
2442
2443 frame.size.height = frame.size.height - [self headerHeight];
2444 frame.origin.y = [self headerHeight];
2445
stkhapuginf58b10d02017-04-10 13:36:172446 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132447 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2448 pageView.center = center;
2449
2450 pageView.backgroundColor = [UIColor whiteColor];
2451 return pageView;
2452}
2453
2454- (void)installDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112455 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
sdefresne49cf2862017-03-15 13:46:142456 // Unregistration happens when the Tab is removed from the TabModel.
Mike Doughertya1ec26402017-08-23 19:46:312457 tab.iOSCaptivePortalBlockingPageDelegate = self;
edchincd32fdf2017-10-25 12:45:452458
2459 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2460 if (PasswordTabHelper* passwordTabHelper =
2461 PasswordTabHelper::FromWebState(tab.webState)) {
2462 passwordTabHelper->SetDispatcher(self.dispatcher);
2463 passwordTabHelper->SetPasswordControllerDelegate(self);
2464 }
2465
sdefresnee65fd872016-12-19 13:38:132466 tab.dialogDelegate = self;
2467 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132468 tab.passKitDialogProvider = self;
Kurt Horimoto803840622017-10-28 01:20:372469 if (!base::FeatureList::IsEnabled(features::kNewFullscreen)) {
2470 tab.fullScreenControllerDelegate = self;
2471 }
sdefresnee65fd872016-12-19 13:38:132472 if (!IsIPadIdiom()) {
2473 tab.overscrollActionsControllerDelegate = self;
2474 }
olivierrobin9ce77b82017-01-12 17:29:192475 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132476 tab.tabSnapshottingDelegate = self;
2477 // Install the proper CRWWebController delegates.
2478 tab.webController.nativeProvider = self;
2479 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512480 // BrowserViewController presents SKStoreKitViewController on behalf of a
2481 // tab.
2482 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2483 if (tabHelper)
2484 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132485 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532486 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292487 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532488 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292489 }
Sylvain Defresnecacc3a52017-09-12 13:51:042490 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592491 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572492 NetExportTabHelper::CreateForWebState(tab.webState, self);
edchincd32fdf2017-10-25 12:45:452493
2494 if (AccountConsistencyService* accountConsistencyService =
2495 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2496 self.browserState)) {
2497 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552498 }
sdefresnee65fd872016-12-19 13:38:132499}
2500
sdefresne49cf2862017-03-15 13:46:142501- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112502 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
Mike Doughertya1ec26402017-08-23 19:46:312503 tab.iOSCaptivePortalBlockingPageDelegate = nil;
edchincd32fdf2017-10-25 12:45:452504
2505 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2506 if (PasswordTabHelper* passwordTabHelper =
2507 PasswordTabHelper::FromWebState(tab.webState))
2508 passwordTabHelper->SetDispatcher(nil);
2509
sdefresne49cf2862017-03-15 13:46:142510 tab.dialogDelegate = nil;
2511 tab.snapshotOverlayProvider = nil;
2512 tab.passKitDialogProvider = nil;
Kurt Horimoto803840622017-10-28 01:20:372513 if (!base::FeatureList::IsEnabled(features::kNewFullscreen)) {
2514 tab.fullScreenControllerDelegate = nil;
2515 }
sdefresne49cf2862017-03-15 13:46:142516 if (!IsIPadIdiom()) {
2517 tab.overscrollActionsControllerDelegate = nil;
2518 }
2519 tab.tabHeadersDelegate = nil;
2520 tab.tabSnapshottingDelegate = nil;
2521 tab.webController.nativeProvider = nil;
2522 tab.webController.swipeRecognizerProvider = nil;
2523 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2524 if (tabHelper)
2525 tabHelper->SetLauncher(nil);
2526 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452527 if (AccountConsistencyService* accountConsistencyService =
2528 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2529 self.browserState)) {
2530 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2531 }
sdefresne49cf2862017-03-15 13:46:142532}
2533
sdefresnee65fd872016-12-19 13:38:132534// Called when a tab is selected in the model. Make any required view changes.
2535// The notification will not be sent when the tab is already the selected tab.
2536- (void)tabSelected:(Tab*)tab {
2537 DCHECK(tab);
2538
2539 // Ignore changes while the tab stack view is visible (or while suspended).
2540 // The display will be refreshed when this view becomes active again.
2541 if (!self.visible || ![_model webUsageEnabled])
2542 return;
2543
2544 [self displayTab:tab isNewSelection:YES];
2545
kkhorimotoa44349c12017-04-12 23:02:122546 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132547 // Now that the new tab has been displayed, return to normal. Rather than
2548 // keep a reference to the previous tab, just turn off preview mode for all
2549 // tabs (since doing so is a no-op for the tabs that don't have it set).
2550 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272551 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132552 [tab.webController setOverlayPreviewMode:NO];
2553 }
2554 }
2555}
2556
edchinf5150c682017-09-18 02:50:032557- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2558 return [self.tabStripCoordinator placeholderView];
2559}
2560
Sylvain Defresne41170aa2017-06-15 10:25:202561- (void)shutdown {
2562 DCHECK(!_isShutdown);
2563 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032564 [self.tabStripCoordinator stop];
2565 self.tabStripCoordinator = nil;
2566 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202567 _infoBarContainer = nil;
2568 _readingListMenuNotifier = nil;
2569 if (_bookmarkModel)
2570 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2571 [_model removeObserver:self];
2572 [[UpgradeCenter sharedInstance] unregisterClient:self];
2573 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052574 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202575 if (_voiceSearchController)
2576 _voiceSearchController->SetDelegate(nil);
2577 [_rateThisAppDialog setDelegate:nil];
2578 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462579 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202580}
2581
sdefresnee65fd872016-12-19 13:38:132582#pragma mark - SnapshotOverlayProvider methods
2583
2584- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2585 NSMutableArray* overlays = [NSMutableArray array];
2586 if (![_model webUsageEnabled]) {
2587 return overlays;
2588 }
2589 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2590 if (voiceSearchView) {
2591 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272592 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132593 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272594 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132595 [overlays addObject:voiceSearchOverlay];
2596 }
2597 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2598 if (infoBarView) {
2599 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272600 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132601 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272602 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132603 [overlays addObject:infoBarOverlay];
2604 }
2605 return overlays;
2606}
2607
2608#pragma mark -
2609
2610- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2611 if (IsIPadIdiom()) {
2612 // Not using overlays on iPad because the content is pushed down by
2613 // infobar and the transition between snapshot and fresh page can
2614 // cause both snapshot and real infobars to appear at the same time.
2615 return nil;
2616 }
2617 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302618 if (currentTab && tab == currentTab) {
2619 DCHECK(currentTab.webState);
2620 infobars::InfoBarManager* infoBarManager =
2621 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132622 if (infoBarManager->infobar_count() > 0) {
2623 DCHECK(_infoBarContainer);
2624 return _infoBarContainer->view();
2625 }
2626 }
2627 return nil;
2628}
2629
2630- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272631 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132632 // There is no UI representation for non-current tabs or there is
2633 // no _infoBarContainer instantiated yet.
2634 // Return offset outside of tab.
2635 return CGRectGetMaxY(self.view.frame);
2636 } else if (IsIPadIdiom()) {
2637 // The infobars on iPad are display at the top of a tab.
2638 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2639 } else {
2640 // The infobars on iPhone are displayed at the bottom of a tab.
2641 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2642 return CGRectGetMaxY(visibleFrame) -
2643 CGRectGetHeight(_infoBarContainer->view().frame);
2644 }
2645}
2646
2647- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2648 Tab* currentTab = [_model currentTab];
2649 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2650 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2651 return _voiceSearchBar;
2652 }
2653 return nil;
2654}
2655
2656- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2657 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2658 // There is no UI representation for non-current tabs or there is
2659 // no visible voice search. Return offset outside of tab.
2660 return CGRectGetMaxY(self.view.frame);
2661 } else {
2662 // The voice search bar on iPhone is displayed at the bottom of a tab.
2663 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2664 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2665 }
2666}
2667
2668- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272669 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132670 VoiceSearchProvider* provider =
2671 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2672 if (provider) {
2673 _voiceSearchController =
2674 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462675 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052676 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132677 }
2678 }
2679}
2680
2681- (void)ensureVoiceSearchBarCreated {
2682 if (_voiceSearchBar)
2683 return;
2684
2685 CGFloat width = CGRectGetWidth([[self view] bounds]);
2686 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2687 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272688 _voiceSearchBar = ios::GetChromeBrowserProvider()
2689 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002690 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132691 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2692 [_voiceSearchBar setHidden:YES];
2693 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2694 UIViewAutoresizingFlexibleWidth];
2695 [self.view insertSubview:_voiceSearchBar
2696 belowSubview:_infoBarContainer->view()];
2697}
2698
2699- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2700 // Voice search bar exists and is shown/hidden.
2701 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272702 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132703 return;
2704
2705 // Voice search bar doesn't exist and thus is not visible.
2706 if (!_voiceSearchBar && !show)
2707 return;
2708
2709 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272710 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132711 else
stkhapuginc9eee7b2017-04-10 15:49:272712 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132713}
2714
2715- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2716 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2717 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2718 self.shouldShowVoiceSearchBar) {
2719 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2720 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272721 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132722 }
2723 id currentNativeController =
2724 [self nativeControllerForTab:self.tabModel.currentTab];
2725 Protocol* possibleOwnerProtocol =
2726 @protocol(LogoAnimationControllerOwnerOwner);
2727 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2728 [currentNativeController logoAnimationControllerOwner]) {
2729 // If the current native controller is showing a GLIF view (e.g. the NTP
2730 // when there is no doodle), use that GLIFControllerOwner.
2731 return [currentNativeController logoAnimationControllerOwner];
2732 }
2733 return nil;
2734}
2735
2736#pragma mark - PassKitDialogProvider methods
2737
2738- (void)presentPassKitDialog:(NSData*)data {
2739 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272740 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132741 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272742 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132743 if (error || !data) {
2744 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302745 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132746 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302747 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132748 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2749 // NULL, replace if with DCHECK).
2750 if (infoBarManager)
2751 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2752 }
2753 } else {
2754 PKAddPassesViewController* passKitViewController =
2755 [_dependencyFactory newPassKitViewControllerForPass:pass];
2756 if (passKitViewController) {
2757 [self presentViewController:passKitViewController
2758 animated:YES
2759 completion:^{
2760 }];
2761 }
2762 }
2763}
2764
2765- (UIStatusBarStyle)preferredStatusBarStyle {
2766 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2767 : UIStatusBarStyleDefault;
2768}
2769
Tomasz Garbusb844e992017-09-29 12:44:552770#pragma mark - PasswordControllerDelegate methods
2771
2772- (BOOL)displaySignInNotification:(UIViewController*)viewController
2773 fromTabId:(NSString*)tabId {
2774 // Check if the call comes from currently visible tab.
2775 if ([tabId isEqual:[_model currentTab].tabId]) {
2776 [self addChildViewController:viewController];
2777 [self.view addSubview:viewController.view];
2778 [viewController didMoveToParentViewController:self];
2779 return YES;
2780 } else {
2781 return NO;
2782 }
2783}
2784
sdefresnee65fd872016-12-19 13:38:132785#pragma mark - CRWWebStateDelegate methods.
2786
eugenebut75a06fa72017-01-09 17:09:552787- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512788 createNewWebStateForURL:(const GURL&)URL
2789 openerURL:(const GURL&)openerURL
2790 initiatedByUser:(BOOL)initiatedByUser {
2791 // Check if requested web state is a popup and block it if necessary.
2792 if (!initiatedByUser) {
2793 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2794 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102795 // It's possible for a page to inject a popup into a window created via
2796 // window.open before its initial load is committed. Rather than relying
2797 // on the last committed or pending NavigationItem's referrer policy, just
2798 // use ReferrerPolicyDefault.
2799 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2800 // policy once referrer policies are correctly recorded in
2801 // NavigationItems.
2802 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512803 helper->HandlePopup(URL, referrer);
2804 return nil;
2805 }
2806 }
2807
2808 // Requested web state should not be blocked from opening.
2809 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2810 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2811
2812 // Tabs open by DOM are always renderer initiated.
2813 web::NavigationManager::WebLoadParams params(GURL{});
2814 params.transition_type = ui::PAGE_TRANSITION_LINK;
2815 params.is_renderer_initiated = true;
2816 Tab* childTab = [[self tabModel]
2817 insertTabWithLoadParams:params
2818 opener:currentTab
2819 openedByDOM:YES
2820 atIndex:TabModelConstants::kTabPositionAutomatically
2821 inBackground:NO];
2822 return childTab.webState;
2823}
2824
eugenebutb46b2122017-03-14 02:43:262825- (void)closeWebState:(web::WebState*)webState {
2826 // Only allow a web page to close itself if it was opened by DOM, or if there
2827 // are no navigation items.
2828 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582829 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262830
2831 if (![self tabModel])
2832 return;
2833
2834 NSUInteger index = [[self tabModel] indexOfTab:tab];
2835 if (index != NSNotFound)
2836 [[self tabModel] closeTabAtIndex:index];
2837}
2838
eugenebut275f5892017-03-09 22:20:512839- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552840 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2841 switch (params.disposition) {
2842 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2843 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2844 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352845 insertTabWithURL:params.url
2846 referrer:params.referrer
2847 transition:params.transition
2848 opener:LegacyTabHelper::GetTabForWebState(webState)
2849 openedByDOM:NO
2850 atIndex:TabModelConstants::kTabPositionAutomatically
2851 inBackground:(params.disposition ==
2852 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552853 return tab.webState;
2854 }
2855 case WindowOpenDisposition::CURRENT_TAB: {
2856 web::NavigationManager::WebLoadParams loadParams(params.url);
2857 loadParams.referrer = params.referrer;
2858 loadParams.transition_type = params.transition;
2859 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2860 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2861 return webState;
2862 }
eugenebutd0984e82017-02-22 23:47:512863 case WindowOpenDisposition::NEW_POPUP: {
2864 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352865 insertTabWithURL:params.url
2866 referrer:params.referrer
2867 transition:params.transition
2868 opener:LegacyTabHelper::GetTabForWebState(webState)
2869 openedByDOM:YES
2870 atIndex:TabModelConstants::kTabPositionAutomatically
2871 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512872 return tab.webState;
2873 }
eugenebut75a06fa72017-01-09 17:09:552874 default:
2875 NOTIMPLEMENTED();
2876 return nullptr;
2877 };
2878}
2879
Mike Dougherty4e6b3a32017-08-23 18:49:212880- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132881 handleContextMenu:(const web::ContextMenuParams&)params {
2882 // Prevent context menu from displaying for a tab which is no longer the
2883 // current one.
2884 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212885 return;
sdefresnee65fd872016-12-19 13:38:132886 }
2887
2888 // No custom context menu if no valid url is available in |params|.
2889 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212890 return;
sdefresnee65fd872016-12-19 13:38:132891 }
2892
2893 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132894
stkhapuginc9eee7b2017-04-10 15:49:272895 _contextMenuCoordinator =
2896 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2897 params:params];
sdefresnee65fd872016-12-19 13:38:132898
2899 NSString* title = nil;
2900 ProceduralBlock action = nil;
2901
stkhapuginc9eee7b2017-04-10 15:49:272902 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132903 GURL link = params.link_url;
2904 bool isLink = link.is_valid();
2905 GURL imageUrl = params.src_url;
2906 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192907 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132908
2909 if (isLink) {
2910 if (link.SchemeIs(url::kJavaScriptScheme)) {
2911 // Open
2912 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2913 action = ^{
2914 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2915 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2916 };
2917 [_contextMenuCoordinator addItemWithTitle:title action:action];
2918 }
2919
2920 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192921 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132922
sdefresnee65fd872016-12-19 13:38:132923 // Open in New Tab.
2924 title = l10n_util::GetNSStringWithFixup(
2925 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2926 action = ^{
2927 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002928 // The "New Tab" item in the context menu opens a new tab in the current
2929 // browser state. |isOffTheRecord| indicates whether or not the current
2930 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132931 [weakSelf webPageOrderedOpen:link
2932 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002933 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132934 inBackground:YES
2935 appendTo:kCurrentTab];
2936 };
2937 [_contextMenuCoordinator addItemWithTitle:title action:action];
2938 if (!_isOffTheRecord) {
2939 // Open in Incognito Tab.
2940 title = l10n_util::GetNSStringWithFixup(
2941 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2942 action = ^{
2943 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2944 [weakSelf webPageOrderedOpen:link
2945 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132946 inIncognito:YES
2947 inBackground:NO
2948 appendTo:kCurrentTab];
2949 };
2950 [_contextMenuCoordinator addItemWithTitle:title action:action];
2951 }
olivierrobin51d4cf42017-01-17 13:32:352952 }
gambard65d69152017-03-23 17:44:222953 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352954 NSString* innerText = params.link_text;
2955 if ([innerText length] > 0) {
2956 // Add to reading list.
2957 title = l10n_util::GetNSStringWithFixup(
2958 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2959 action = ^{
2960 Record(ACTION_READ_LATER, isImage, isLink);
2961 [weakSelf addToReadingListURL:link title:innerText];
2962 };
2963 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532964 }
sdefresnee65fd872016-12-19 13:38:132965 }
2966 // Copy Link.
2967 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2968 action = ^{
2969 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282970 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132971 };
2972 [_contextMenuCoordinator addItemWithTitle:title action:action];
2973 }
2974 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192975 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132976 // Save Image.
gambard98b4ddf2017-04-18 07:14:052977 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132978 action = ^{
2979 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2980 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2981 };
2982 [_contextMenuCoordinator addItemWithTitle:title action:action];
2983 // Open Image.
2984 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2985 action = ^{
2986 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2987 [weakSelf loadURL:imageUrl
2988 referrer:referrer
2989 transition:ui::PAGE_TRANSITION_LINK
2990 rendererInitiated:YES];
2991 };
2992 [_contextMenuCoordinator addItemWithTitle:title action:action];
2993 // Open Image In New Tab.
2994 title = l10n_util::GetNSStringWithFixup(
2995 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2996 action = ^{
2997 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2998 [weakSelf webPageOrderedOpen:imageUrl
2999 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133000 inBackground:true
3001 appendTo:kCurrentTab];
3002 };
3003 [_contextMenuCoordinator addItemWithTitle:title action:action];
3004
3005 TemplateURLService* service =
3006 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103007 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133008 if (defaultURL && !defaultURL->image_url().empty() &&
3009 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3010 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3011 defaultURL->short_name());
3012 action = ^{
3013 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3014 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3015 };
3016 [_contextMenuCoordinator addItemWithTitle:title action:action];
3017 }
3018 }
3019
3020 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133021}
3022
eugenebutb739bdc2017-01-25 06:32:483023- (void)webState:(web::WebState*)webState
3024 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3025 // Display the action sheet with the arrow pointing at the top center of the
3026 // web contents.
sdefresne0452a9d2017-02-09 15:33:283027 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483028 UIView* view = webState->GetView();
3029 CGPoint dialogLocation =
3030 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283031 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033032 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173033 helper->PresentDialog(dialogLocation,
3034 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053035 handler(shouldContinue);
3036 }));
eugenebutb739bdc2017-01-25 06:32:483037}
3038
sdefresnee65fd872016-12-19 13:38:133039- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3040 (web::WebState*)webState {
3041 return _javaScriptDialogPresenter.get();
3042}
3043
eugenebut63232102017-01-19 16:19:403044- (void)webState:(web::WebState*)webState
3045 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3046 proposedCredential:(NSURLCredential*)proposedCredential
3047 completionHandler:(void (^)(NSString* username,
3048 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:423049 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3050 if ([tab isPrerenderTab]) {
3051 [tab discardPrerender];
3052 if (handler) {
3053 handler(nil, nil);
3054 }
3055 return;
3056 }
3057
eugenebut63232102017-01-19 16:19:403058 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3059 proposedCredential:proposedCredential
3060 webState:webState
3061 completionHandler:handler];
3062}
3063
sdefresnee65fd872016-12-19 13:38:133064#pragma mark - FullScreenControllerDelegate methods
3065
3066- (CGFloat)headerOffset {
3067 if (IsIPadIdiom())
3068 return StatusBarHeight();
3069 return 0.0;
3070}
3071
stkhapugin952ecef2017-04-11 12:11:453072- (NSArray<HeaderDefinition*>*)headerViews {
3073 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133074 if (![self isViewLoaded])
3075 return results;
3076
3077 if (!IsIPadIdiom()) {
sczsf1620e52017-10-02 22:54:463078 if ([_toolbarCoordinator view]) {
stkhapugin952ecef2017-04-11 12:11:453079 [results addObject:[HeaderDefinition
sczsf1620e52017-10-02 22:54:463080 definitionWithView:[_toolbarCoordinator view]
stkhapugin952ecef2017-04-11 12:11:453081 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243082 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453083 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133084 }
3085 } else {
edchinf5150c682017-09-18 02:50:033086 if (self.tabStripView) {
3087 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3088 headerBehaviour:Hideable
3089 heightAdjustment:0.0
3090 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133091 }
sczsf1620e52017-10-02 22:54:463092 if ([_toolbarCoordinator view]) {
stkhapugin952ecef2017-04-11 12:11:453093 [results addObject:[HeaderDefinition
sczsf1620e52017-10-02 22:54:463094 definitionWithView:[_toolbarCoordinator view]
stkhapugin952ecef2017-04-11 12:11:453095 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243096 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453097 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133098 }
3099 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453100 [results addObject:[HeaderDefinition
3101 definitionWithView:[_findBarController view]
3102 headerBehaviour:Overlap
3103 heightAdjustment:0.0
3104 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133105 }
3106 }
stkhapugin952ecef2017-04-11 12:11:453107 return [results copy];
sdefresnee65fd872016-12-19 13:38:133108}
3109
3110- (UIView*)footerView {
3111 return _voiceSearchBar;
3112}
3113
3114- (CGFloat)headerHeight {
3115 return [self headerHeightForTab:[_model currentTab]];
3116}
3117
3118- (CGFloat)headerHeightForTab:(Tab*)tab {
3119 id nativeController = [self nativeControllerForTab:tab];
3120 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3121 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3122 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3123 // On iPhone, don't add any header height for ToolbarOwner native
3124 // controllers when they're displaying their own toolbar.
3125 return 0;
3126 }
3127
stkhapugin952ecef2017-04-11 12:11:453128 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133129
3130 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453131 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133132 if (header.view && header.behaviour == Hideable) {
3133 height += CGRectGetHeight([header.view frame]) -
3134 header.heightAdjustement - header.inset;
3135 }
3136 }
3137
3138 return height - StatusBarHeight();
3139}
3140
3141- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193142 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133143}
3144
3145- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453146 NSArray<HeaderDefinition*>* headers = [self headerViews];
3147 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133148 return 0.0;
3149
3150 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3151 // API documentation.
3152 if ([[[self tabModel] currentTab] isPrerenderTab])
3153 return [self headerHeight];
3154
3155 UIView* topHeader = headers[0].view;
3156 return -(topHeader.frame.origin.y - [self headerOffset]);
3157}
3158
3159- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3160 UIView* footer = [self footerView];
3161 CGFloat headerHeight = [self headerHeight];
3162 if (!footer || headerHeight == 0)
3163 return 0.0;
3164
3165 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3166 CGFloat offset = headerOffset * footerHeight / headerHeight;
3167 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3168}
3169
3170- (void)fullScreenController:(FullScreenController*)controller
3171 headerAnimationCompleted:(BOOL)completed
3172 offset:(CGFloat)offset {
3173 if (completed)
justincohen04c27772016-12-21 20:16:593174 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133175}
3176
stkhapugin952ecef2017-04-11 12:11:453177- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133178 atOffset:(CGFloat)headerOffset {
3179 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453180 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133181 CGRect frame = [header.view frame];
3182 frame.origin.y = height - headerOffset - header.inset;
3183 [header.view setFrame:frame];
3184 if (header.behaviour != Overlap)
3185 height += CGRectGetHeight(frame);
3186 }
3187}
3188
3189- (void)fullScreenController:(FullScreenController*)fullScreenController
3190 drawHeaderViewFromOffset:(CGFloat)headerOffset
3191 animate:(BOOL)animate {
3192 if ([_sideSwipeController inSwipe])
3193 return;
3194
3195 CGRect footerFrame = CGRectZero;
3196 UIView* footer = nil;
3197 // Only animate the voice search bar if the tab is a voice search results tab.
3198 if ([_model currentTab].isVoiceSearchResultsTab) {
3199 footer = [self footerView];
3200 footerFrame = footer.frame;
3201 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3202 }
3203
stkhapugin952ecef2017-04-11 12:11:453204 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133205 void (^block)(void) = ^{
3206 [self setFramesForHeaders:headers atOffset:headerOffset];
3207 footer.frame = footerFrame;
3208 };
3209 void (^completion)(BOOL) = ^(BOOL finished) {
3210 [self fullScreenController:fullScreenController
3211 headerAnimationCompleted:finished
3212 offset:headerOffset];
3213 };
3214 if (animate) {
Sylvain Defresneed8c0db2017-08-31 16:29:523215 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133216 delay:0.0
3217 options:UIViewAnimationOptionBeginFromCurrentState
3218 animations:block
3219 completion:completion];
3220 } else {
3221 block();
3222 completion(YES);
3223 }
3224}
3225
3226- (void)fullScreenController:(FullScreenController*)fullScreenController
3227 drawHeaderViewFromOffset:(CGFloat)headerOffset
3228 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3229 changeTopContentPadding:(BOOL)changeTopContentPadding
3230 scrollingToOffset:(CGFloat)contentOffset {
3231 DCHECK(webViewProxy);
3232 if ([_sideSwipeController inSwipe])
3233 return;
3234
3235 CGRect footerFrame;
3236 UIView* footer = nil;
3237 // Only animate the voice search bar if the tab is a voice search results tab.
3238 if ([_model currentTab].isVoiceSearchResultsTab) {
3239 footer = [self footerView];
3240 footerFrame = footer.frame;
3241 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3242 }
3243
stkhapugin952ecef2017-04-11 12:11:453244 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133245 void (^block)(void) = ^{
3246 [self setFramesForHeaders:headers atOffset:headerOffset];
3247 footer.frame = footerFrame;
3248 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3249 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3250 if (changeTopContentPadding)
3251 webViewProxy.topContentPadding = contentOffset;
3252 };
3253 void (^completion)(BOOL) = ^(BOOL finished) {
3254 [self fullScreenController:fullScreenController
3255 headerAnimationCompleted:finished
3256 offset:headerOffset];
3257 };
3258
Sylvain Defresneed8c0db2017-08-31 16:29:523259 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133260 delay:0.0
3261 options:UIViewAnimationOptionBeginFromCurrentState
3262 animations:block
3263 completion:completion];
3264}
3265
3266#pragma mark - VoiceSearchBarOwner
3267
3268- (id<VoiceSearchBar>)voiceSearchBar {
3269 return _voiceSearchBar;
3270}
3271
3272#pragma mark - Install OverScrollActionController method.
3273- (void)setOverScrollActionControllerToStaticNativeContent:
3274 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343275 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133276 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173277 [[OverscrollActionsController alloc]
3278 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133279 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053280 OverscrollStyle style = _isOffTheRecord
3281 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3282 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133283 controller.style = style;
3284 nativeContent.overscrollActionsController = controller;
3285 }
3286}
3287
3288#pragma mark - OverscrollActionsControllerDelegate methods.
3289
3290- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053291 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133292 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053293 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383294 [self.dispatcher
3295 openNewTab:[OpenNewTabCommand
3296 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133297 break;
rohitrao922b7111c2017-01-03 14:31:053298 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243299 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133300 break;
liaoyuke563dc4a2017-03-17 18:36:293301 case OverscrollAction::REFRESH: {
liaoyuke563dc4a2017-03-17 18:36:293302 web::WebState* webState = [_model currentTab].webState;
Eugene But083b6c7a2017-10-02 15:49:383303 if (webState) {
3304 if (webState->IsLoading()) {
3305 webState->Stop();
3306 }
liaoyuke563dc4a2017-03-17 18:36:293307 // |check_for_repost| is true because the reload is explicitly initiated
3308 // by the user.
3309 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3310 true /* check_for_repost */);
Eugene But083b6c7a2017-10-02 15:49:383311 }
sdefresnee65fd872016-12-19 13:38:133312 break;
liaoyuke563dc4a2017-03-17 18:36:293313 }
rohitrao922b7111c2017-01-03 14:31:053314 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133315 NOTREACHED();
3316 break;
3317 }
3318}
3319
3320- (BOOL)shouldAllowOverscrollActions {
3321 return YES;
3322}
3323
3324- (UIView*)headerView {
sczsf1620e52017-10-02 22:54:463325 return [_toolbarCoordinator view];
sdefresnee65fd872016-12-19 13:38:133326}
3327
3328- (UIView*)toolbarSnapshotView {
sczsf1620e52017-10-02 22:54:463329 return [[_toolbarCoordinator view] snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133330}
3331
3332- (CGFloat)overscrollActionsControllerHeaderInset:
3333 (OverscrollActionsController*)controller {
3334 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3335 return [self headerHeight];
3336 else
3337 return 0;
3338}
3339
3340- (CGFloat)overscrollHeaderHeight {
3341 return [self headerHeight] + StatusBarHeight();
3342}
3343
3344#pragma mark - TabSnapshottingDelegate methods.
3345
3346- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3347 CGRect pageContentArea = _contentArea.bounds;
3348 if ([_model webUsageEnabled])
3349 pageContentArea = tab.view.bounds;
3350 CGFloat headerHeight = [self headerHeightForTab:tab];
3351 id nativeController = [self nativeControllerForTab:tab];
3352 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3353 headerHeight += [nativeController toolbarHeight];
3354 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3355 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3356}
3357
3358#pragma mark - NewTabPageObserver methods.
3359
3360- (void)selectedPanelDidChange {
3361 [self updateToolbar];
3362}
3363
3364#pragma mark - CRWNativeContentProvider methods
3365
3366- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3367 withError:(NSError*)error
3368 isPost:(BOOL)isPost {
3369 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173370 [[ErrorPageContent alloc] initWithLoader:self
3371 browserState:self.browserState
3372 url:url
3373 error:error
3374 isPost:isPost
3375 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133376 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3377 return errorPageContent;
3378}
3379
3380- (BOOL)hasControllerForURL:(const GURL&)url {
3381 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453382 if (host == kChromeUIOfflineHost) {
3383 // Only allow offline URL that are fully specified.
3384 return reading_list::IsOfflineURLValid(
3385 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3386 }
sdefresnee65fd872016-12-19 13:38:133387
Justin Cohen8679e852017-08-14 16:35:253388 if (host == kChromeUIBookmarksHost) {
Marti Wong2a43bb92017-10-10 12:55:243389 // Only allow bookmark URL on iPad when Bookmarks is not shown modally.
3390 return IsIPadIdiom() && !PresentNTPPanelModally();
Justin Cohen8679e852017-08-14 16:35:253391 }
3392
3393 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133394}
3395
olivierrobind43eecb2017-01-27 20:35:263396- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3397 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133398 DCHECK(url.SchemeIs(kChromeUIScheme));
3399
3400 id<CRWNativeContent> nativeController = nil;
3401 std::string url_host = url.host();
Justin Cohen49715952017-08-22 14:12:193402 if (url_host == kChromeUINewTabHost ||
Marti Wong2a43bb92017-10-10 12:55:243403 (IsIPadIdiom() && url_host == kChromeUIBookmarksHost &&
3404 !PresentNTPPanelModally())) {
Gauthier Ambardd8890452017-09-29 12:07:463405 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3406 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3407 if (@available(iOS 11.0, *)) {
3408 safeAreaInset = self.view.safeAreaInsets;
3409 }
3410 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3411
sdefresnee65fd872016-12-19 13:38:133412 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173413 [[NewTabPageController alloc] initWithUrl:url
3414 loader:self
sczsf1620e52017-10-02 22:54:463415 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173416 ntpObserver:self
3417 browserState:_browserState
3418 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463419 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453420 tabModel:_model
justincohen75011c32017-04-28 16:31:393421 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463422 dispatcher:self.dispatcher
3423 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133424 pageController.swipeRecognizerProvider = self.sideSwipeController;
3425
3426 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233427 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133428
Marti Wong2a43bb92017-10-10 12:55:243429 if (IsIPadIdiom() && !PresentNTPPanelModally()) {
sdefresnee65fd872016-12-19 13:38:133430 // New Tab Page can have multiple panels. Each panel is addressable
3431 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3432 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3433 // the Bookmark Manager, etc.
3434 // The utility functions NewTabPage::IdentifierFromFragment() and
3435 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3436 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3437 // without changing the URL since the URL may be chrome://bookmarks/#123.
3438 // If the URL is chrome://newtab/, pre-select the panel based on the
3439 // #fragment.
3440 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233441 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133442 : NewTabPage::IdentifierFromFragment(url.ref());
3443 }
3444 [pageController selectPanel:panelType];
3445 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453446 } else if (url_host == kChromeUIOfflineHost &&
3447 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133448 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173449 [[OfflinePageNativeContent alloc] initWithLoader:self
3450 browserState:_browserState
3451 webState:webState
3452 URL:url];
sdefresnee65fd872016-12-19 13:38:133453 [self setOverScrollActionControllerToStaticNativeContent:
3454 staticNativeController];
3455 nativeController = staticNativeController;
3456 } else if (url_host == kChromeUIExternalFileHost) {
3457 // Return an instance of the |ExternalFileController| only if the file is
3458 // still in the sandbox.
3459 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3460 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173461 nativeController =
3462 [[ExternalFileController alloc] initWithURL:url
3463 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133464 }
peterlaurens44615d02017-05-23 20:23:093465 } else if (url_host == kChromeUICrashHost) {
3466 // There is no native controller for kChromeUICrashHost, it is instead
3467 // handled as any other renderer crash by the SadTabTabHelper.
3468 // nativeController must be set to nil to prevent defaulting to a
3469 // PageNotAvailableController.
3470 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133471 } else {
3472 DCHECK(![self hasControllerForURL:url]);
3473 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173474 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133475 }
3476 // If a native controller is vended before its tab is added to the tab model,
3477 // use the temporary key and add it under the new tab's tabId in the
3478 // TabModelObserver callback. This happens:
3479 // - when there is no current tab (occurs when vending the NTP controller for
3480 // the first tab that is opened),
3481 // - when the current tab's url doesn't match |url| (occurs when a native
3482 // controller is opened in a new tab)
3483 // - when the current tab's url matches |url| and there is already a native
3484 // controller of the appropriate type vended to it (occurs when a native
3485 // controller is opened in a new tab from a tab with a matching URL, e.g.
3486 // opening an NTP when an NTP is already displayed in the current tab).
3487 // For normal page loads, history navigations, tab restorations, and crash
3488 // recoveries, the tab will already exist in the tab model and the tabId can
3489 // be used as the native controller key.
3490 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3491 // that native controllers vended here always correspond to the current tab.
3492 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193493 if (!currentTab.webState ||
3494 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443495 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133496 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443497 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133498 }
sdefresnee65fd872016-12-19 13:38:133499 return nativeController;
3500}
3501
3502- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443503 id nativeController = tab.webController.nativeController;
3504 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133505}
3506
3507#pragma mark - DialogPresenterDelegate methods
3508
3509- (void)dialogPresenter:(DialogPresenter*)presenter
3510 willShowDialogForWebState:(web::WebState*)webState {
3511 for (Tab* iteratedTab in self.tabModel) {
3512 if ([iteratedTab webState] == webState) {
3513 self.tabModel.currentTab = iteratedTab;
3514 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3515 break;
3516 }
3517 }
3518}
3519
3520#pragma mark - Context menu methods
3521
3522- (void)searchByImageAtURL:(const GURL&)url
3523 referrer:(const web::Referrer)referrer {
3524 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273525 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113526 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173527 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3528 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113529 DCHECK(data);
3530 dispatch_async(dispatch_get_main_queue(), ^{
3531 [weakSelf searchByImageData:data atURL:image_source_url];
3532 });
3533 };
3534 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133535 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3536 web::PolicyForNavigation(url, referrer));
3537}
3538
3539- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3540 NSData* imageData = data;
3541 UIImage* image = [UIImage imageWithData:imageData];
3542 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3543 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3544 // kSearchByImageMaxImageHeight).
3545 if (image &&
3546 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3547 (image.size.width > kSearchByImageMaxImageWidth ||
3548 image.size.height > kSearchByImageMaxImageHeight)) {
3549 CGSize newImageSize =
3550 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3551 image = [image gtm_imageByResizingToSize:newImageSize
3552 preserveAspectRatio:YES
3553 trimToFit:NO];
3554 imageData = UIImageJPEGRepresentation(image, 1.0);
3555 }
3556
3557 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3558 std::string byteString(bytes, [imageData length]);
3559
3560 TemplateURLService* templateUrlService =
3561 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103562 const TemplateURL* defaultURL =
3563 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133564 DCHECK(!defaultURL->image_url().empty());
3565 DCHECK(defaultURL->image_url_ref().IsValid(
3566 templateUrlService->search_terms_data()));
3567 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3568 search_args.image_url = imageURL;
3569 search_args.image_thumbnail_content = byteString;
3570
3571 // Generate the URL and populate |post_content| with the content type and
3572 // HTTP body for the request.
3573 TemplateURLRef::PostContent post_content;
3574 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3575 search_args, templateUrlService->search_terms_data(), &post_content));
3576 [self addSelectedTabWithURL:result
3577 postData:&post_content
3578 transition:ui::PAGE_TRANSITION_TYPED];
3579}
3580
3581- (void)saveImageAtURL:(const GURL&)url
3582 referrer:(const web::Referrer&)referrer {
3583 DCHECK(url.is_valid());
3584
gambard9efce7a2017-02-09 18:53:173585 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3586 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113587 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133588
gambardbbf85c42017-06-29 11:15:343589 if ([data length] == 0) {
3590 [self displayPrivacyErrorAlertOnMainQueue:
3591 l10n_util::GetNSString(
3592 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3593 return;
3594 }
3595
gambard9efce7a2017-02-09 18:53:173596 base::FilePath::StringType extension;
3597
3598 bool extensionSuccess =
3599 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3600 if (!extensionSuccess || extension.length() == 0) {
3601 extension = "png";
3602 }
3603
3604 NSString* fileExtension =
3605 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3606 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113607 };
3608 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133609 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3610 web::PolicyForNavigation(url, referrer));
3611}
3612
gambard9efce7a2017-02-09 18:53:173613- (void)managePermissionAndSaveImage:(NSData*)data
3614 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133615 switch ([PHPhotoLibrary authorizationStatus]) {
3616 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173617 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133618 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3619 // Call -saveImage again to check if chrome needs to display an error or
3620 // saves the image.
3621 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173622 [self managePermissionAndSaveImage:data
3623 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133624 }];
3625 break;
stkhapuginf58b10d02017-04-10 13:36:173626 }
sdefresnee65fd872016-12-19 13:38:133627
3628 // The application doesn't have permission to access photo and the user
3629 // cannot grant it.
3630 case PHAuthorizationStatusRestricted:
3631 [self displayPrivacyErrorAlertOnMainQueue:
3632 l10n_util::GetNSString(
3633 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3634 break;
3635
3636 // The application doesn't have permission to access photo and the user
3637 // can grant it.
3638 case PHAuthorizationStatusDenied:
3639 [self displayImageErrorAlertWithSettingsOnMainQueue];
3640 break;
3641
3642 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243643 default:
3644 __weak BrowserViewController* weakSelf = self;
3645 [self saveImage:data
3646 withFileExtension:fileExtension
3647 completion:^(BOOL success, NSError* error) {
3648 [weakSelf finishSavingImageWithError:error];
3649 }];
sdefresnee65fd872016-12-19 13:38:133650 break;
sdefresnee65fd872016-12-19 13:38:133651 }
3652}
3653
Sylvain Defresnefd3ecf22017-07-12 18:47:243654- (void)saveImage:(NSData*)data
3655 withFileExtension:(NSString*)fileExtension
3656 completion:(void (^)(BOOL, NSError*))completion {
3657 base::PostTaskWithTraits(
3658 FROM_HERE,
3659 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3660 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3661 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373662 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133663
Sylvain Defresnefd3ecf22017-07-12 18:47:243664 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3665 stringByAppendingString:fileExtension];
3666 NSURL* fileURL = [NSURL
3667 fileURLWithPath:[NSTemporaryDirectory()
3668 stringByAppendingPathComponent:fileName]];
3669 NSError* error = nil;
3670 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3671 if (error) {
3672 if (completion)
3673 completion(NO, error);
3674 return;
3675 }
sdefresnee65fd872016-12-19 13:38:133676
Sylvain Defresnefd3ecf22017-07-12 18:47:243677 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3678 [PHAssetChangeRequest
3679 creationRequestForAssetFromImageAtFileURL:fileURL];
3680 }
3681 completionHandler:^(BOOL success, NSError* error) {
3682 base::PostTaskWithTraits(
3683 FROM_HERE,
3684 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3685 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3686 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373687 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243688 if (completion)
3689 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133690
Sylvain Defresnefd3ecf22017-07-12 18:47:243691 // Cleanup the temporary file.
3692 NSError* deleteFileError = nil;
3693 [[NSFileManager defaultManager]
3694 removeItemAtURL:fileURL
3695 error:&deleteFileError];
3696 }));
3697 }];
3698 }));
sdefresnee65fd872016-12-19 13:38:133699}
3700
3701- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3702 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3703 BOOL canGoToSetting =
3704 [[UIApplication sharedApplication] canOpenURL:settingURL];
3705 if (canGoToSetting) {
3706 dispatch_async(dispatch_get_main_queue(), ^{
3707 [self displayImageErrorAlertWithSettings:settingURL];
3708 });
3709 } else {
3710 [self displayPrivacyErrorAlertOnMainQueue:
3711 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3712 }
3713}
3714
3715- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3716 // Dismiss current alert.
3717 [_alertCoordinator stop];
3718
3719 NSString* title =
3720 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3721 NSString* message = l10n_util::GetNSString(
3722 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3723
stkhapuginc9eee7b2017-04-10 15:49:273724 _alertCoordinator =
3725 [[AlertCoordinator alloc] initWithBaseViewController:self
3726 title:title
3727 message:message];
sdefresnee65fd872016-12-19 13:38:133728
3729 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3730 action:nil
3731 style:UIAlertActionStyleCancel];
3732
3733 [_alertCoordinator
3734 addItemWithTitle:l10n_util::GetNSString(
3735 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3736 action:^{
3737 OpenUrlWithCompletionHandler(settingURL, nil);
3738 }
3739 style:UIAlertActionStyleDefault];
3740
3741 [_alertCoordinator start];
3742}
3743
3744- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3745 dispatch_async(dispatch_get_main_queue(), ^{
3746 NSString* title =
3747 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3748 [self showErrorAlertWithStringTitle:title message:errorContent];
3749 });
3750}
3751
3752// This callback is triggered when the image is effectively saved onto the photo
3753// album, or if the save failed for some reason.
3754- (void)finishSavingImageWithError:(NSError*)error {
3755 // Was there an error?
3756 if (error) {
3757 // Saving photo failed even though user has granted access to Photos.
3758 // Display the error information from the NSError object for user.
3759 NSString* errorMessage = [NSString
3760 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3761 [error domain], [error code]];
3762 // This code may be execute outside of the main thread. Make sure to display
3763 // the error on the main thread.
3764 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3765 } else {
3766 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3767 // the photo application. The current behaviour is to create the photo there
3768 // but not providing any link to it is suboptimal. That's what Safari is
3769 // doing, and what the PM want, but it doesn't make it right.
3770 }
3771}
3772
sdefresnee65fd872016-12-19 13:38:133773#pragma mark - Showing popups
3774
sdefresnee65fd872016-12-19 13:38:133775- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133776 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3777
3778 ReadingListModel* readingModel =
3779 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013780 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3781 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133782
pinkerton07e27842017-03-02 15:29:023783 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363784 [self showSnackbar:l10n_util::GetNSString(
3785 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133786}
3787
3788#pragma mark - Keyboard commands management
3789
3790- (BOOL)shouldRegisterKeyboardCommands {
3791 if ([self presentedViewController])
3792 return NO;
3793
3794 if (_voiceSearchController && _voiceSearchController->IsVisible())
3795 return NO;
3796
3797 // If there is no first responder, try to make the webview the first
3798 // responder.
3799 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303800 web::WebState* webState = _model.currentTab.webState;
3801 if (webState)
3802 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133803 }
3804
3805 return YES;
3806}
3807
3808- (KeyCommandsProvider*)keyCommandsProvider {
3809 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273810 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133811 }
stkhapuginc9eee7b2017-04-10 15:49:273812 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133813}
3814
3815#pragma mark - KeyCommandsPlumbing
3816
3817- (BOOL)isOffTheRecord {
3818 return _isOffTheRecord;
3819}
3820
3821- (NSUInteger)tabsCount {
3822 return [_model count];
3823}
3824
lpromero47ea8862017-01-13 17:51:063825- (BOOL)canGoBack {
3826 return [_model currentTab].canGoBack;
3827}
3828
3829- (BOOL)canGoForward {
3830 return [_model currentTab].canGoForward;
3831}
3832
sdefresnee65fd872016-12-19 13:38:133833- (void)focusTabAtIndex:(NSUInteger)index {
3834 if ([_model count] > index) {
3835 [_model setCurrentTab:[_model tabAtIndex:index]];
3836 }
3837}
3838
3839- (void)focusNextTab {
3840 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3841 NSInteger modelCount = [_model count];
3842 if (currentTabIndex < modelCount - 1) {
3843 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3844 [_model setCurrentTab:nextTab];
3845 } else {
3846 [_model setCurrentTab:[_model tabAtIndex:0]];
3847 }
3848}
3849
3850- (void)focusPreviousTab {
3851 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3852 if (currentTabIndex > 0) {
3853 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3854 [_model setCurrentTab:previousTab];
3855 } else {
3856 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3857 [_model setCurrentTab:lastTab];
3858 }
3859}
3860
3861- (void)reopenClosedTab {
3862 sessions::TabRestoreService* const tabRestoreService =
3863 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3864 if (!tabRestoreService || tabRestoreService->entries().empty())
3865 return;
3866
3867 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3868 tabRestoreService->entries().front();
3869 // Only handle the TAB type.
3870 if (entry->type != sessions::TabRestoreService::TAB)
3871 return;
3872
Mark Cogandfcdea72017-07-18 13:47:383873 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133874 TabRestoreServiceDelegateImplIOS* const delegate =
3875 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3876 _browserState);
3877 tabRestoreService->RestoreEntryById(delegate, entry->id,
3878 WindowOpenDisposition::CURRENT_TAB);
3879}
3880
3881- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463882 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133883}
3884
3885#pragma mark - UIResponder
3886
3887- (NSArray*)keyCommands {
3888 if (![self shouldRegisterKeyboardCommands]) {
3889 return nil;
3890 }
3891 return [self.keyCommandsProvider
3892 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543893 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243894 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133895 editingText:![self isFirstResponder]];
3896}
3897
3898#pragma mark -
3899
3900// Induce an intentional crash in the browser process.
3901- (void)induceBrowserCrash {
3902 CHECK(false);
3903 // Call another function, so that the above CHECK can't be tail-call
3904 // optimized. This ensures that this method's name will show up in the stack
3905 // for easier identification.
3906 CHECK(true);
3907}
3908
3909- (void)loadURL:(const GURL&)url
3910 referrer:(const web::Referrer&)referrer
3911 transition:(ui::PageTransition)transition
3912 rendererInitiated:(BOOL)rendererInitiated {
3913 [[OmniboxGeolocationController sharedInstance]
3914 locationBarDidSubmitURL:url
3915 transition:transition
3916 browserState:_browserState];
3917
3918 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3919 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3920 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3921 }
3922
3923 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243924 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133925 // restart.
3926 if (url.host() == kChromeUIBrowserCrashHost) {
3927 [self induceBrowserCrash];
3928 // In debug the app can continue working even after the CHECK. Adding a
3929 // return avoids the crash url to be added to the history.
3930 return;
3931 }
3932
Danyao Wang85389a82017-10-25 18:56:273933 bool typed_or_generated_transition =
3934 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3935 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3936
Rohit Rao44f204302017-08-10 14:49:543937 PrerenderService* prerenderService =
3938 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3939 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593940 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543941 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593942 DCHECK(newWebState);
3943
sdefresnee65fd872016-12-19 13:38:133944 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593945 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133946 DCHECK(oldTab);
3947 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593948
kkhorimotod804c5732017-03-15 23:44:523949 bool canPruneItems =
3950 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593951
kkhorimotod804c5732017-03-15 23:44:523952 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523953 [newTab navigationManager]->CopyStateFromAndPrune(
3954 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593955
3956 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3957 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133958
3959 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3960 // BrowserViewController to detect that a pre-rendered tab is switched in,
3961 // and show the prerendering animation.
3962 newTab.isPrerenderTab = NO;
Danyao Wang85389a82017-10-25 18:56:273963 if (typed_or_generated_transition) {
3964 LoadTimingTabHelper::FromWebState(newTab.webState)
3965 ->DidPromotePrerenderTab();
3966 }
sdefresnee65fd872016-12-19 13:38:133967
sdefresne2f7781c2017-03-02 19:12:463968 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133969 return;
3970 }
3971 }
3972
3973 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543974 if (prerenderService) {
3975 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133976 }
3977
sdefresnee65fd872016-12-19 13:38:133978 // Some URLs are not allowed while in incognito. If we are in incognito and
3979 // load a disallowed URL, instead create a new tab not in the incognito state.
3980 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3981 [self webPageOrderedOpen:url
3982 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133983 inIncognito:NO
3984 inBackground:NO
3985 appendTo:kCurrentTab];
3986 return;
3987 }
3988
Danyao Wang85389a82017-10-25 18:56:273989 if (typed_or_generated_transition) {
3990 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
3991 ->DidInitiatePageLoad();
3992 }
3993
mrefaata84d5a02017-06-08 17:13:293994 // If this is a reload initiated from the omnibox.
3995 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3996 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3997 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3998 [[_model currentTab] navigationManager]->Reload(
3999 web::ReloadType::NORMAL, true /* check_for_repost */);
4000 return;
4001 }
4002
sdefresnee65fd872016-12-19 13:38:134003 web::NavigationManager::WebLoadParams params(urlToLoad);
4004 params.referrer = referrer;
4005 params.transition_type = transition;
4006 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104007 Tab* currentTab = [_model currentTab];
4008 DCHECK(currentTab);
4009 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4010 currentTab.navigationManager->LoadURLWithParams(params);
4011 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4012 // to be animated on screen because the transition animator will handle the
4013 // animations. When a Tab stops being a voice search Tab, the voice search
4014 // bar should be animated away.
4015 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4016 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134017}
4018
4019- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544020 PrerenderService* prerenderService =
4021 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4022 if (prerenderService) {
4023 prerenderService->CancelPrerender();
4024 }
sdefresnee65fd872016-12-19 13:38:134025 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184026 if ([self currentWebState])
4027 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134028}
4029
4030- (web::WebState*)currentWebState {
4031 return [[_model currentTab] webState];
4032}
4033
sdefresnee65fd872016-12-19 13:38:134034// Load a new URL on a new page/tab.
4035- (void)webPageOrderedOpen:(const GURL&)URL
4036 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134037 inBackground:(BOOL)inBackground
4038 appendTo:(OpenPosition)appendTo {
4039 Tab* adjacentTab = nil;
4040 if (appendTo == kCurrentTab)
4041 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354042 [_model insertTabWithURL:URL
4043 referrer:referrer
4044 transition:ui::PAGE_TRANSITION_LINK
4045 opener:adjacentTab
4046 openedByDOM:NO
4047 atIndex:TabModelConstants::kTabPositionAutomatically
4048 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134049}
4050
4051- (void)webPageOrderedOpen:(const GURL&)url
4052 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134053 inIncognito:(BOOL)inIncognito
4054 inBackground:(BOOL)inBackground
4055 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004056 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234057 // feature_engagement::Tracker based on |inIncognito|.
4058 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004059
sdefresnee65fd872016-12-19 13:38:134060 if (inIncognito == _isOffTheRecord) {
4061 [self webPageOrderedOpen:url
4062 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134063 inBackground:inBackground
4064 appendTo:appendTo];
4065 return;
4066 }
4067 // When sending an open command that switches modes, ensure the tab
4068 // ends up appended to the end of the model, not just next to what is
4069 // currently selected in the other mode. This is done with the |append|
4070 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274071 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134072 initWithURL:url
4073 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134074 inIncognito:inIncognito
4075 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274076 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154077 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134078}
4079
4080- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054081 WebStateList* webStateList = [_model webStateList];
4082 webStateList->ReplaceWebStateAt(
4083 webStateList->active_index(),
4084 session_util::CreateWebStateWithNavigationEntries(
4085 [_model browserState], sessionTab->current_navigation_index,
4086 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134087}
4088
4089- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434090 DCHECK(javascript);
4091 javascript = [javascript stringByRemovingPercentEncoding];
4092 web::WebState* webState = [[_model currentTab] webState];
4093 if (webState) {
4094 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4095 }
sdefresnee65fd872016-12-19 13:38:134096}
4097
4098#pragma mark - WebToolbarDelegate methods
4099
4100- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
4101 if (_locationBarHasFocus)
4102 return; // TODO(crbug.com/244366): This should not be necessary.
4103 _locationBarHasFocus = YES;
4104 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524105 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134106 object:nil];
4107 [_sideSwipeController setEnabled:NO];
4108 if ([[_model currentTab].webController wantsKeyboardShield]) {
4109 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4110 [_typingShield setAlpha:0.0];
4111 [_typingShield setHidden:NO];
4112 [UIView animateWithDuration:0.3
4113 animations:^{
4114 [_typingShield setAlpha:1.0];
4115 }];
4116 }
4117 [[OmniboxGeolocationController sharedInstance]
4118 locationBarDidBecomeFirstResponder:_browserState];
4119}
4120
4121- (IBAction)locationBarDidResignFirstResponder:(id)sender {
4122 if (!_locationBarHasFocus)
4123 return; // TODO(crbug.com/244366): This should not be necessary.
4124 _locationBarHasFocus = NO;
4125 [_sideSwipeController setEnabled:YES];
4126 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524127 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134128 object:nil];
4129 [UIView animateWithDuration:0.3
4130 animations:^{
4131 [_typingShield setAlpha:0.0];
4132 }
4133 completion:^(BOOL finished) {
4134 // This can happen if one quickly resigns the omnibox and then taps
4135 // on the omnibox again during this animation. If the animation is
4136 // interrupted and the toolbar controller is first responder, it's safe
4137 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464138 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134139 return;
4140 [_typingShield setHidden:YES];
4141 }];
4142 [[OmniboxGeolocationController sharedInstance]
4143 locationBarDidResignFirstResponder:_browserState];
4144
4145 // If a load was cancelled by an omnibox edit, but nothing is loading when
4146 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4147 if (_locationBarEditCancelledLoad) {
4148 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294149
4150 web::WebState* webState = [_model currentTab].webState;
4151 if (!_toolbarModelIOS->IsLoading() && webState)
4152 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4153 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134154 }
4155}
4156
4157- (IBAction)locationBarBeganEdit:(id)sender {
4158 // On handsets, if a page is currently loading it should be stopped.
4159 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354160 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134161 _locationBarEditCancelledLoad = YES;
4162 }
4163}
4164
sdefresnee65fd872016-12-19 13:38:134165- (ToolbarModelIOS*)toolbarModelIOS {
4166 return _toolbarModelIOS.get();
4167}
4168
sdefresnee65fd872016-12-19 13:38:134169- (void)willUpdateToolbarSnapshot {
4170 [[_model currentTab].overscrollActionsController clear];
4171}
4172
4173- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4174 CGRect frame = [_contentArea frame];
4175 if (!fullScreen) {
4176 // Changing the origin here is unnecessary, it's set in page_animation_util.
4177 frame.size.height -= [self headerHeight];
4178 }
4179
4180 CGFloat shortAxis = frame.size.width;
4181 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524182 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134183 CGFloat aspectRatio = frame.size.height / frame.size.width;
4184 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4185 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4186 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4187 CGRect cardFrame = {frame.origin, cardSize};
4188
4189 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174190 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134191 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4192 : CardCloseButtonSide::LEADING;
4193 [_contentArea addSubview:card];
4194 return card;
4195}
4196
Mark Cogan6ebbde02017-07-07 12:50:134197#pragma mark - BrowserCommands
4198
4199- (void)goBack {
4200 [[_model currentTab] goBack];
4201}
4202
4203- (void)goForward {
4204 [[_model currentTab] goForward];
4205}
4206
Mark Coganb9aac6432017-07-07 13:26:354207- (void)stopLoading {
4208 [_model currentTab].webState->Stop();
4209}
4210
4211- (void)reload {
4212 web::WebState* webState = [_model currentTab].webState;
4213 if (webState) {
4214 // |check_for_repost| is true because the reload is explicitly initiated
4215 // by the user.
4216 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4217 true /* check_for_repost */);
4218 }
4219}
4220
Mark Cogan8e791022017-07-10 09:55:354221- (void)bookmarkPage {
4222 [self initializeBookmarkInteractionController];
4223 [_bookmarkInteractionController
4224 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064225 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354226}
4227
Mark Cogan6acee7f2017-07-11 09:01:404228- (void)showToolsMenu {
4229 DCHECK(_browserState);
4230 DCHECK(self.visible || self.dismissingModal);
4231
4232 // Record the time this menu was requested; to be stored in the configuration
4233 // object.
4234 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4235
4236 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:464237 [_toolbarCoordinator cancelOmniboxEdit];
Mark Cogan6acee7f2017-07-11 09:01:404238 // Dismiss the soft keyboard (if open).
4239 [[_model currentTab].webController dismissKeyboard];
4240 // Dismiss Find in Page focus.
4241 [self updateFindBar:NO shouldFocus:NO];
4242
4243 ToolsMenuConfiguration* configuration =
edchin8e4cfe032017-10-25 13:25:544244 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4245 baseViewController:self];
Mark Cogan6acee7f2017-07-11 09:01:404246 configuration.requestStartTime =
4247 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4248 if ([_model count] == 0)
4249 [configuration setNoOpenedTabs:YES];
4250
4251 if (_isOffTheRecord)
4252 [configuration setInIncognito:YES];
4253
4254 if (!_readingListMenuNotifier) {
4255 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4256 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4257 _browserState)];
4258 }
Cooper Knaake4f495cf2017-07-27 23:30:034259
4260 feature_engagement::Tracker* engagementTracker =
4261 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4262 if (engagementTracker->ShouldTriggerHelpUI(
4263 feature_engagement::kIPHBadgedReadingListFeature)) {
4264 [configuration setShowReadingListNewBadge:YES];
4265 [configuration setEngagementTracker:engagementTracker];
4266 }
Mark Cogan6acee7f2017-07-11 09:01:404267 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4268
4269 [configuration setUserAgentType:self.userAgentType];
4270
Helen Yang9175bd52017-08-12 00:28:404271 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4272 [configuration setHighlightNewIncognitoTabCell:YES];
4273 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4274 }
4275
4276 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4277 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4278 }
4279
sczsf1620e52017-10-02 22:54:464280 [_toolbarCoordinator showToolsMenuPopupWithConfiguration:configuration];
Mark Cogan6acee7f2017-07-11 09:01:404281
4282 ToolsPopupController* toolsPopupController =
sczsf1620e52017-10-02 22:54:464283 [_toolbarCoordinator toolsPopupController];
Mark Cogan6acee7f2017-07-11 09:01:404284 if ([_model currentTab]) {
4285 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4286 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4287 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404288 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4289
4290 if (!IsIPadIdiom())
4291 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4292 }
4293}
4294
Mark Cogandfcdea72017-07-18 13:47:384295- (void)openNewTab:(OpenNewTabCommand*)command {
4296 if (self.isOffTheRecord != command.incognito) {
4297 // Not for this browser state, send it on its way.
4298 [self.dispatcher switchModesAndOpenNewTab:command];
4299 return;
4300 }
4301
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004302 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234303 // events to the feature_engagement::Tracker based on |command.userInitiated|
4304 // and |command.incognito|.
4305 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004306
Mark Cogandfcdea72017-07-18 13:47:384307 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4308 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494309 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4310 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384311 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494312 if (oldForegroundTabWasAddedCompletionBlock) {
4313 oldForegroundTabWasAddedCompletionBlock();
4314 }
Mark Cogandfcdea72017-07-18 13:47:384315 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4316 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4317 if (offTheRecord) {
4318 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4319 timeDelta);
4320 } else {
4321 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4322 }
4323 };
4324
4325 [self setLastTapPoint:command];
4326 DCHECK(self.visible || self.dismissingModal);
4327 Tab* currentTab = [_model currentTab];
4328 if (currentTab) {
4329 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4330 }
4331 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4332 transition:ui::PAGE_TRANSITION_TYPED];
4333}
4334
Mark Cogan123895002017-07-20 12:54:064335- (void)printTab {
4336 Tab* currentTab = [_model currentTab];
4337 // The UI should prevent users from printing non-printable pages. However, a
4338 // redirection to an un-printable page can happen before it is reflected in
4339 // the UI.
4340 if (![currentTab viewForPrinting]) {
4341 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364342 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064343 return;
4344 }
4345 DCHECK(_browserState);
4346 if (!_printController) {
4347 _printController = [[PrintController alloc]
4348 initWithContextGetter:_browserState->GetRequestContext()];
4349 }
4350 [_printController printView:[currentTab viewForPrinting]
4351 withTitle:[currentTab title]
4352 viewController:self];
4353}
4354
Mark Coganfa25b052017-07-20 17:31:034355- (void)addToReadingList:(ReadingListAddCommand*)command {
4356 [self addToReadingListURL:[command URL] title:[command title]];
4357}
4358
sczs3a8c8602017-08-01 20:14:084359- (void)showReadingList {
4360 _readingListCoordinator = [[ReadingListCoordinator alloc]
4361 initWithBaseViewController:self
4362 browserState:self.browserState
4363 loader:self];
4364
4365 [_readingListCoordinator start];
4366}
4367
Jean-François Geyelinedef9552017-08-07 09:56:564368- (void)preloadVoiceSearch {
4369 // Preload VoiceSearchController and views and view controllers needed
4370 // for voice search.
4371 [self ensureVoiceSearchControllerCreated];
4372 _voiceSearchController->PrepareToAppear();
4373}
4374
edchinc5720722017-08-14 22:06:314375#if !defined(NDEBUG)
4376- (void)viewSource {
4377 Tab* tab = [_model currentTab];
4378 DCHECK(tab);
4379 CRWWebController* webController = tab.webController;
4380 NSString* script = @"document.documentElement.outerHTML;";
4381 __weak Tab* weakTab = tab;
4382 __weak BrowserViewController* weakSelf = self;
4383 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4384 Tab* strongTab = weakTab;
4385 if (!strongTab)
4386 return;
4387 if (![result isKindOfClass:[NSString class]])
4388 result = @"Not an HTML page";
4389 std::string base64HTML;
4390 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4391 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194392 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314393 web::ReferrerPolicyDefault);
4394
4395 [[weakSelf tabModel]
4396 insertTabWithURL:URL
4397 referrer:referrer
4398 transition:ui::PAGE_TRANSITION_LINK
4399 opener:strongTab
4400 openedByDOM:YES
4401 atIndex:TabModelConstants::kTabPositionAutomatically
4402 inBackground:NO];
4403 };
4404 [webController executeJavaScript:script
4405 completionHandler:completionHandlerBlock];
4406}
4407#endif // !defined(NDEBUG)
4408
edchin2134c042017-08-18 13:57:354409// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4410- (void)showRateThisAppDialog {
4411 DCHECK(!_rateThisAppDialog);
4412
4413 // Store the current timestamp whenever this dialog is shown.
4414 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4415 base::Time::Now().ToInternalValue());
4416
Gregory Chatzinofff39ec5162017-10-05 20:28:534417 // iOS11 no longer supports the itms link to the app store. So, use a deep
4418 // link for iOS11 and the itms link for prior versions.
4419 NSURL* storeURL;
4420 if (base::ios::IsRunningOnIOS11OrLater()) {
4421 storeURL =
4422 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4423 @"google-chrome-the-fast-and-secure-web-browser/"
4424 @"id535886823?action=write-review")];
4425 } else {
4426 storeURL = [NSURL
4427 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4428 @"MZStore.woa/wa/"
4429 @"viewContentsUserReviews?type=Purple+Software&id="
4430 @"535886823&pt=9008&ct=rating")];
4431 }
edchin2134c042017-08-18 13:57:354432
4433 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014434 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354435
4436 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4437 [_rateThisAppDialog setAppStoreURL:storeURL];
4438 [_rateThisAppDialog setDelegate:self];
4439 [_rateThisAppDialog show];
4440}
4441
Gregory Chatzinoff3f40c1542017-08-30 07:50:044442- (void)showFindInPage {
4443 if (!self.canShowFindBar)
4444 return;
4445
4446 if (!_findBarController) {
4447 _findBarController =
4448 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4449 _findBarController.dispatcher = self.dispatcher;
4450 }
4451
4452 Tab* tab = [_model currentTab];
4453 DCHECK(tab);
4454 auto* helper = FindTabHelper::FromWebState(tab.webState);
4455 DCHECK(!helper->IsFindUIActive());
4456 helper->SetFindUIActive(true);
4457 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4458}
4459
4460- (void)closeFindInPage {
4461 __weak BrowserViewController* weakSelf = self;
4462 Tab* currentTab = [_model currentTab];
4463 if (currentTab) {
4464 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4465 [weakSelf updateFindBar:NO shouldFocus:NO];
4466 });
4467 }
4468}
4469
4470- (void)searchFindInPage {
4471 DCHECK([_model currentTab]);
4472 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4473 __weak BrowserViewController* weakSelf = self;
4474 helper->StartFinding(
4475 [_findBarController searchTerm], ^(FindInPageModel* model) {
4476 BrowserViewController* strongSelf = weakSelf;
4477 if (!strongSelf) {
4478 return;
4479 }
4480 [strongSelf->_findBarController updateResultsCount:model];
4481 });
4482
4483 if (!_isOffTheRecord)
4484 helper->PersistSearchTerm();
4485}
4486
4487- (void)findNextStringInPage {
4488 Tab* currentTab = [_model currentTab];
4489 DCHECK(currentTab);
4490 // TODO(crbug.com/603524): Reshow find bar if necessary.
4491 FindTabHelper::FromWebState(currentTab.webState)
4492 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4493 [_findBarController updateResultsCount:model];
4494 });
4495}
4496
4497- (void)findPreviousStringInPage {
4498 Tab* currentTab = [_model currentTab];
4499 DCHECK(currentTab);
4500 // TODO(crbug.com/603524): Reshow find bar if necessary.
4501 FindTabHelper::FromWebState(currentTab.webState)
4502 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4503 [_findBarController updateResultsCount:model];
4504 });
4505}
4506
edchinf84b2502017-08-31 21:30:454507- (void)showHelpPage {
4508 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4509 [self webPageOrderedOpen:helpUrl
4510 referrer:web::Referrer()
4511 inBackground:NO
4512 appendTo:kCurrentTab];
4513}
4514
edchinb59b5602017-09-01 15:00:204515- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104516 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204517 [self showAllBookmarks];
4518 } else {
4519 [self initializeBookmarkInteractionController];
4520 [_bookmarkInteractionController presentBookmarks];
4521 }
4522}
4523
edchin8ee0807d2017-09-01 23:52:474524- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104525 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474526 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4527 } else {
4528 if (!self.recentTabsCoordinator) {
4529 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4530 initWithBaseViewController:self];
4531 self.recentTabsCoordinator.loader = self;
4532 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4533 self.recentTabsCoordinator.browserState = _browserState;
4534 }
4535 [self.recentTabsCoordinator start];
4536 }
4537}
4538
Mark Cogan6de7e9a2017-09-06 12:57:214539- (void)requestDesktopSite {
4540 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4541}
4542
4543- (void)requestMobileSite {
4544 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4545}
4546
sdefresnee65fd872016-12-19 13:38:134547#pragma mark - Command Handling
4548
sdefresnee65fd872016-12-19 13:38:134549- (void)closeCurrentTab {
4550 Tab* currentTab = [_model currentTab];
4551 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4552 if (tabIndex == NSNotFound)
4553 return;
4554
jif7fed8122017-02-08 13:15:254555 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4556 // iPad.
sdefresnee65fd872016-12-19 13:38:134557 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4558 exitingPage.image =
4559 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4560
4561 // Close the actual tab, and add its image as a subview.
4562 [_model closeTabAtIndex:tabIndex];
4563
4564 // Do not animate close in iPad.
4565 if (!IsIPadIdiom()) {
4566 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524567 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134568 exitingPage, 0, YES, IsPortrait(), ^{
4569 [exitingPage removeFromSuperview];
4570 });
4571 }
4572}
4573
Elodie Banelaa5ab432017-09-28 14:42:014574- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4575 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434576 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134577 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4578 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014579 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464580 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014581 }
sdefresnee65fd872016-12-19 13:38:134582 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274583 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474584 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134585 if (_voiceSearchController)
4586 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364587
4588 Tab* currentTab = [_model currentTab];
4589 [currentTab dismissModals];
4590
rohitrao005a6432017-03-16 20:52:424591 if (currentTab) {
4592 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4593 if (findHelper) {
4594 findHelper->StopFinding(^{
4595 [self updateFindBar:NO shouldFocus:NO];
4596 });
4597 }
4598 }
rohitraob2bf3cb2017-02-10 14:10:364599
sdefresnee65fd872016-12-19 13:38:134600 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134601 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274602 _printController = nil;
sczsf1620e52017-10-02 22:54:464603 [_toolbarCoordinator dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134604 [_contextMenuCoordinator stop];
4605 [self dismissRateThisAppDialog];
4606
4607 if (self.presentedViewController) {
4608 // Dismisses any other modal controllers that may be present, e.g. Recent
4609 // Tabs.
4610 // Note that currently, some controllers like the bookmark ones were already
4611 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4612 // but are still reported as the presentedViewController. The result is that
4613 // this will call -dismissViewControllerAnimated:completion: a second time
4614 // on it. It is not per se an issue, as it is a no-op. The problem is that
4615 // in such a case, the completion block is not called.
4616 // To ensure the completion is called, nil is passed here, and the
4617 // completion is called below.
4618 [self dismissViewControllerAnimated:NO completion:nil];
4619 // Dismissed controllers will be so after a delay. Queue the completion
4620 // callback after that.
4621 if (completion) {
4622 dispatch_after(
4623 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4624 dispatch_get_main_queue(), ^{
4625 completion();
4626 });
4627 }
4628 } else if (completion) {
4629 // If no view controllers are presented, we should be ok with dispatching
4630 // the completion block directly.
4631 dispatch_async(dispatch_get_main_queue(), completion);
4632 }
4633}
4634
sdefresnee65fd872016-12-19 13:38:134635#pragma mark - Find Bar
4636
4637- (void)hideFindBarWithAnimation:(BOOL)animate {
4638 [_findBarController hideFindBarView:animate];
4639}
4640
4641- (void)showFindBarWithAnimation:(BOOL)animate
4642 selectText:(BOOL)selectText
4643 shouldFocus:(BOOL)shouldFocus {
4644 DCHECK(_findBarController);
4645 Tab* tab = [_model currentTab];
4646 DCHECK(tab);
4647 CRWWebController* webController = tab.webController;
4648
4649 CGRect referenceFrame = CGRectZero;
4650 if (IsIPadIdiom()) {
4651 referenceFrame = webController.visibleFrame;
4652 referenceFrame.origin.y -= kIPadFindBarOverlap;
4653 } else {
4654 referenceFrame = _contentArea.frame;
4655 }
4656
sczsf1620e52017-10-02 22:54:464657 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134658 [_findBarController addFindBarView:animate
4659 intoView:self.view
4660 withFrame:referenceFrame
4661 alignWithFrame:omniboxFrame
4662 selectText:selectText];
4663 [self updateFindBar:YES shouldFocus:shouldFocus];
4664}
4665
sdefresnee65fd872016-12-19 13:38:134666- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324667 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4668 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4669 // in a crash.
4670 if (![_model currentTab]) {
4671 return;
4672 }
rohitrao005a6432017-03-16 20:52:424673 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4674 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134675 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424676 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134677 }
4678
4679 [self setFramesForHeaders:[self headerViews]
4680 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424681 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134682 initialUpdate:initialUpdate
4683 focusTextfield:shouldFocus];
4684 } else {
4685 [self hideFindBarWithAnimation:YES];
4686 }
4687}
4688
4689- (void)showAllBookmarks {
4690 DCHECK(self.visible || self.dismissingModal);
4691 GURL URL(kChromeUIBookmarksURL);
4692 Tab* tab = [_model currentTab];
4693 web::NavigationManager::WebLoadParams params(URL);
4694 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234695 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134696}
4697
Gauthier Ambardf520c022017-08-29 07:42:234698- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134699 DCHECK(self.visible || self.dismissingModal);
4700 GURL url(kChromeUINewTabURL);
4701 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4702 if (fragment != "") {
4703 GURL::Replacements replacement;
4704 replacement.SetRefStr(fragment);
4705 url = url.ReplaceComponents(replacement);
4706 }
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
sdefresnee65fd872016-12-19 13:38:134713- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274714 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134715 base::RecordAction(base::UserMetricsAction(
4716 "IOSRateThisAppDialogDismissedProgramatically"));
4717 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274718 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134719 }
4720}
4721
Jean-François Geyelin5d2e184c2017-07-28 19:48:004722- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4723 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134724 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124725 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134726 _startVoiceSearchAfterNewTabAnimation = YES;
4727 return;
4728 }
4729
4730 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4731 // dismiss the keyboard.
4732 [self closeFindInPage];
4733 [[_model currentTab].webController dismissKeyboard];
4734
4735 // Ensure that voice search objects are created.
4736 [self ensureVoiceSearchControllerCreated];
4737 [self ensureVoiceSearchBarCreated];
4738
4739 // Present voice search.
4740 [_voiceSearchBar prepareToPresentVoiceSearch];
4741 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:464742 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:134743}
4744
4745#pragma mark - ToolbarOwner
4746
4747- (ToolbarController*)relinquishedToolbarController {
4748 if (_isToolbarControllerRelinquished)
4749 return nil;
4750
4751 ToolbarController* relinquishedToolbarController = nil;
sczsf1620e52017-10-02 22:54:464752 if ([_toolbarCoordinator view].hidden) {
sdefresnee65fd872016-12-19 13:38:134753 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:194754 if (currentTab.webState &&
4755 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
sdefresnee65fd872016-12-19 13:38:134756 // Use the native content controller's toolbar when the BVC's is hidden.
4757 id nativeController = [self nativeControllerForTab:currentTab];
4758 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4759 relinquishedToolbarController =
4760 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274761 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134762 }
4763 }
4764 } else {
Gauthier Ambard82c8cc52017-10-26 15:59:054765 relinquishedToolbarController = [_toolbarCoordinator toolbarController];
sdefresnee65fd872016-12-19 13:38:134766 }
4767 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4768 return relinquishedToolbarController;
4769}
4770
4771- (void)reparentToolbarController {
4772 if (_isToolbarControllerRelinquished) {
sczsf1620e52017-10-02 22:54:464773 if ([[_toolbarCoordinator view] isDescendantOfView:self.view]) {
sdefresnee65fd872016-12-19 13:38:134774 // A native content controller's toolbar has been relinquished.
4775 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274776 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134777 } else if ([_findBarController isFindInPageShown]) {
sczsf1620e52017-10-02 22:54:464778 [self.view insertSubview:[_toolbarCoordinator view]
sdefresnee65fd872016-12-19 13:38:134779 belowSubview:[_findBarController view]];
Jean-François Geyelined4cde72017-10-11 11:34:504780 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
4781 [self addConstraintsToToolbar];
4782 }
sdefresnee65fd872016-12-19 13:38:134783 } else {
sczsf1620e52017-10-02 22:54:464784 [self.view addSubview:[_toolbarCoordinator view]];
Jean-François Geyelined4cde72017-10-11 11:34:504785 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
4786 [self addConstraintsToToolbar];
4787 }
sdefresnee65fd872016-12-19 13:38:134788 }
sdefresnee65fd872016-12-19 13:38:134789 _isToolbarControllerRelinquished = NO;
4790 }
4791}
4792
4793#pragma mark - TabModelObserver methods
4794
4795// Observer method, tab inserted.
4796- (void)tabModel:(TabModel*)model
4797 didInsertTab:(Tab*)tab
4798 atIndex:(NSUInteger)modelIndex
4799 inForeground:(BOOL)fg {
4800 DCHECK(tab);
4801 [self installDelegatesForTab:tab];
4802
4803 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194804 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134805 }
4806}
4807
4808// Observer method, active tab changed.
4809- (void)tabModel:(TabModel*)model
4810 didChangeActiveTab:(Tab*)newTab
4811 previousTab:(Tab*)previousTab
4812 atIndex:(NSUInteger)index {
4813 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4814 // Currently this observer method is always called with a non-nil |newTab|,
4815 // but that may change in the future. Remove this DCHECK when it does.
4816 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274817 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304818 DCHECK(newTab.webState);
4819 infobars::InfoBarManager* infoBarManager =
4820 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134821 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4822 }
4823 [self updateVoiceSearchBarVisibilityAnimated:NO];
4824
Mohamad Ahmadi7d09ec32017-07-11 22:32:194825 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134826
4827 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134828}
4829
4830// Observer method, tab changed.
4831- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4832 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4833 if (tab == [_model currentTab]) {
4834 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134835 }
4836}
4837
sdefresne49cf2862017-03-15 13:46:144838// Observer method, tab replaced.
4839- (void)tabModel:(TabModel*)model
4840 didReplaceTab:(Tab*)oldTab
4841 withTab:(Tab*)newTab
4842 atIndex:(NSUInteger)index {
4843 [self uninstallDelegatesForTab:oldTab];
4844 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264845
michaeldo79909fb2017-05-09 23:42:504846 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304847 infobars::InfoBarManager* infoBarManager = nullptr;
4848 if (newTab) {
4849 DCHECK(newTab.webState);
4850 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4851 }
michaeldo79909fb2017-05-09 23:42:504852 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4853 }
4854
kkhorimotofa0844cc2017-03-20 17:01:264855 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4856 if (self.active && model.currentTab == newTab)
4857 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464858
4859 if (newTab)
4860 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144861}
4862
sdefresnee65fd872016-12-19 13:38:134863// A tab has been removed, remove its views from display if necessary.
4864- (void)tabModel:(TabModel*)model
4865 didRemoveTab:(Tab*)tab
4866 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144867 [self uninstallDelegatesForTab:tab];
4868
kkhorimoto496fdd72017-06-12 19:56:314869 // Cancel dialogs for |tab|'s WebState.
4870 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4871
sdefresnee65fd872016-12-19 13:38:134872 // Ignore changes while the tab stack view is visible (or while suspended).
4873 // The display will be refreshed when this view becomes active again.
4874 if (!self.visible || !model.webUsageEnabled)
4875 return;
4876
4877 // Remove the find bar for now.
4878 [self hideFindBarWithAnimation:NO];
4879}
4880
4881- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4882 if (tab == [model currentTab]) {
4883 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464884 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134885 }
4886
Mohamad Ahmadi7d09ec32017-07-11 22:32:194887 [_paymentRequestManager stopTrackingWebState:tab.webState];
4888
sdefresnee65fd872016-12-19 13:38:134889 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4890 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194891 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134892 }
4893}
4894
4895// Called when the number of tabs changes. Update the toolbar accordingly.
4896- (void)tabModelDidChangeTabCount:(TabModel*)model {
4897 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464898 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134899}
4900
4901#pragma mark - Upgrade Detection
4902
4903- (void)showUpgrade:(UpgradeCenter*)center {
4904 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274905 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134906 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304907 DCHECK(tab.webState);
4908 infobars::InfoBarManager* infoBarManager =
4909 InfoBarManagerImpl::FromWebState(tab.webState);
4910 DCHECK(infoBarManager);
4911 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134912 }
4913}
4914
sdefresnee65fd872016-12-19 13:38:134915
4916#pragma mark - InfoBarControllerDelegate
4917
4918- (void)infoBarContainerStateChanged:(bool)isAnimating {
4919 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4920 DCHECK(infoBarContainerView);
4921 CGRect containerFrame = infoBarContainerView.frame;
4922 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4923 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4924 containerFrame.size.height = height;
4925 BOOL isViewVisible = self.visible;
4926 [UIView animateWithDuration:0.1
4927 animations:^{
4928 [infoBarContainerView setFrame:containerFrame];
4929 }
4930 completion:^(BOOL finished) {
4931 if (!isViewVisible)
4932 return;
4933 UIAccessibilityPostNotification(
4934 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4935 }];
4936}
4937
4938- (BOOL)shouldAutorotate {
4939 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4940 // Don't rotate if a voice search is being presented or dismissed. Once the
4941 // transition animations finish, only the Voice Search UIViewController's
4942 // |-shouldAutorotate| will be called.
4943 return NO;
4944 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4945 // Don't auto rotate if side swipe controller view says not to.
4946 return NO;
4947 } else {
4948 return [super shouldAutorotate];
4949 }
4950}
4951
4952// Always return yes, as this tap should work with various recognizers,
4953// including UITextTapRecognizer, UILongPressGestureRecognizer,
4954// UIScrollViewPanGestureRecognizer and others.
4955- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4956 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4957 (UIGestureRecognizer*)otherGestureRecognizer {
4958 return YES;
4959}
4960
4961// Tap gestures should only be recognized within |_contentArea|.
4962- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4963 CGPoint location = [gesture locationInView:self.view];
4964
4965 // Only allow touches on descendant views of |_contentArea|.
4966 UIView* hitView = [self.view hitTest:location withEvent:nil];
4967 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4968}
4969
4970#pragma mark - SideSwipeController Delegate Methods
4971
4972- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4973 DCHECK(!IsIPadIdiom());
4974 // Update frame incase orientation changed while |_contentArea| was out of
4975 // the view hierarchy.
4976 [_contentArea setFrame:[sideSwipeView frame]];
4977
4978 [self.view insertSubview:_contentArea atIndex:0];
4979 [self updateVoiceSearchBarVisibilityAnimated:NO];
4980 [self updateToolbar];
4981
4982 // Reset horizontal stack view.
4983 [sideSwipeView removeFromSuperview];
4984 [_sideSwipeController setInSwipe:NO];
4985 [_infoBarContainer->view() setHidden:NO];
4986}
4987
4988- (UIView*)contentView {
4989 return _contentArea;
4990}
4991
sdefresnee65fd872016-12-19 13:38:134992- (WebToolbarController*)toolbarController {
sczsf1620e52017-10-02 22:54:464993 return _toolbarCoordinator.webToolbarController;
sdefresnee65fd872016-12-19 13:38:134994}
4995
4996- (BOOL)preventSideSwipe {
sczsf1620e52017-10-02 22:54:464997 if ([_toolbarCoordinator toolsPopupController])
sdefresnee65fd872016-12-19 13:38:134998 return YES;
4999
5000 if (_voiceSearchController && _voiceSearchController->IsVisible())
5001 return YES;
5002
sdefresnee65fd872016-12-19 13:38:135003 if (!self.active)
5004 return YES;
5005
5006 return NO;
5007}
5008
5009- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5010 if (visible) {
5011 [self updateVoiceSearchBarVisibilityAnimated:NO];
5012 [self updateToolbar];
5013 [_infoBarContainer->view() setHidden:NO];
5014 } else {
5015 // Hide UI accessories such as find bar and first visit overlays
5016 // for welcome page.
5017 [self hideFindBarWithAnimation:NO];
5018 [_infoBarContainer->view() setHidden:YES];
5019 [_voiceSearchBar setHidden:YES];
5020 }
5021}
5022
5023- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5024 BOOL seenToolbar = NO;
5025 BOOL seenInfoBarContainer = NO;
5026 BOOL seenContentArea = NO;
5027 for (UIView* view in views.subviews) {
sczsf1620e52017-10-02 22:54:465028 if (view == [_toolbarCoordinator view])
sdefresnee65fd872016-12-19 13:38:135029 seenToolbar = YES;
5030 else if (view == _infoBarContainer->view())
5031 seenInfoBarContainer = YES;
5032 else if (view == _contentArea)
5033 seenContentArea = YES;
5034 if ((seenToolbar && !seenInfoBarContainer) ||
5035 (seenInfoBarContainer && !seenContentArea))
5036 return NO;
5037 }
5038 return YES;
5039}
5040
5041#pragma mark - PreloadControllerDelegate methods
5042
rohitraoeeb5293b2017-06-15 14:40:025043- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445044 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135045}
5046
rohitraoeeb5293b2017-06-15 14:40:025047- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5048 return [self hasControllerForURL:url];
5049}
5050
sdefresnee65fd872016-12-19 13:38:135051#pragma mark - BookmarkBridgeMethods
5052
5053// If an added or removed bookmark is the same as the current url, update the
5054// toolbar so the star highlight is kept in sync.
5055- (void)bookmarkNodeModified:(const BookmarkNode*)node {
Sylvain Defresnee7f2c8a2017-10-17 02:39:195056 if ([_model currentTab].webState &&
5057 node->url() == [_model currentTab].webState->GetLastCommittedURL()) {
sdefresnee65fd872016-12-19 13:38:135058 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255059 }
sdefresnee65fd872016-12-19 13:38:135060}
5061
5062// If all bookmarks are removed, update the toolbar so the star highlight is
5063// kept in sync.
5064- (void)allBookmarksRemoved {
5065 [self updateToolbar];
5066}
5067
sdefresnee65fd872016-12-19 13:38:135068- (void)showErrorAlertWithStringTitle:(NSString*)title
5069 message:(NSString*)message {
5070 // Dismiss current alert.
5071 [_alertCoordinator stop];
5072
stkhapuginc9eee7b2017-04-10 15:49:275073 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5074 message:message
5075 viewController:self];
sdefresnee65fd872016-12-19 13:38:135076 [_alertCoordinator start];
5077}
5078
edchineeb4d422017-10-02 17:39:365079- (void)showSnackbar:(NSString*)text {
5080 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5081 message.accessibilityLabel = text;
5082 message.duration = 2.0;
5083 message.category = kBrowserViewControllerSnackbarCategory;
5084 [self.dispatcher showSnackbarMessage:message];
5085}
5086
sdefresnee65fd872016-12-19 13:38:135087#pragma mark - Show Mail Composer methods
5088
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575089- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5090 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135091 if (![MFMailComposeViewController canSendMail]) {
5092 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575093 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135094 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575095 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135096 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5097 return;
5098 }
stkhapuginc9eee7b2017-04-10 15:49:275099 MFMailComposeViewController* mailViewController =
5100 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135101 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575102 [mailViewController setToRecipients:[context toRecipients]];
5103 [mailViewController setSubject:[context subject]];
5104 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135105
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575106 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135107 if (!textFile.empty()) {
5108 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5109 NSData* data = [NSData dataWithContentsOfFile:filename];
5110 if (data) {
5111 NSString* displayName =
5112 base::SysUTF8ToNSString(textFile.BaseName().value());
5113 [mailViewController addAttachmentData:data
5114 mimeType:@"text/plain"
5115 fileName:displayName];
5116 }
5117 }
5118
5119 [mailViewController setMailComposeDelegate:self];
5120 [self presentViewController:mailViewController animated:YES completion:nil];
5121}
5122
5123#pragma mark - MFMailComposeViewControllerDelegate methods
5124
5125- (void)mailComposeController:(MFMailComposeViewController*)controller
5126 didFinishWithResult:(MFMailComposeResult)result
5127 error:(NSError*)error {
5128 [self dismissViewControllerAnimated:YES completion:nil];
5129}
5130
5131#pragma mark - StoreKitLauncher methods
5132
5133- (void)productViewControllerDidFinish:
5134 (SKStoreProductViewController*)viewController {
5135 [self dismissViewControllerAnimated:YES completion:nil];
5136}
5137
5138- (void)openAppStore:(NSString*)appId {
5139 if (![appId length])
5140 return;
5141 NSDictionary* product =
5142 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275143 SKStoreProductViewController* storeViewController =
5144 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135145 [storeViewController setDelegate:self];
5146 [storeViewController loadProductWithParameters:product completionBlock:nil];
5147 [self presentViewController:storeViewController animated:YES completion:nil];
5148}
5149
5150#pragma mark - TabDialogDelegate methods
5151
sdefresnee65fd872016-12-19 13:38:135152- (void)cancelDialogForTab:(Tab*)tab {
5153 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5154}
5155
5156#pragma mark - FKFeedbackPromptDelegate methods
5157
5158- (void)userTappedRateApp:(UIView*)view {
5159 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275160 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135161}
5162
5163- (void)userTappedSendFeedback:(UIView*)view {
5164 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275165 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205166 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135167}
5168
5169- (void)userTappedDismiss:(UIView*)view {
5170 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275171 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135172}
5173
5174#pragma mark - VoiceSearchBarDelegate
5175
5176- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275177 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135178 [self ensureVoiceSearchControllerCreated];
5179 return _voiceSearchController->IsTextToSpeechEnabled() &&
5180 _voiceSearchController->IsTextToSpeechSupported();
5181}
5182
5183- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275184 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135185 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5186}
5187
5188#pragma mark - VoiceSearchPresenter
5189
5190- (UIView*)voiceSearchButton {
5191 return _voiceSearchButton;
5192}
5193
5194- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5195 return [self currentLogoAnimationControllerOwner];
5196}
5197
Rohit Rao01e0e002017-08-14 20:49:435198#pragma mark - ActivityService Providers
5199
5200- (void)presentActivityServiceViewController:(UIViewController*)controller {
5201 [self presentViewController:controller animated:YES completion:nil];
5202}
5203
5204- (void)activityServiceDidEndPresenting {
5205 self.presenting = NO;
5206 [self.dialogPresenter tryToPresent];
5207}
5208
Rohit Raocda0a992017-08-16 15:37:115209#pragma mark - QRScanner Requirements
5210
5211- (void)presentQRScannerViewController:(UIViewController*)controller {
5212 [self presentViewController:controller animated:YES completion:nil];
5213}
5214
5215- (void)dismissQRScannerViewController:(UIViewController*)controller
5216 completion:(void (^)(void))completion {
5217 DCHECK_EQ(controller, self.presentedViewController);
5218 [self dismissViewControllerAnimated:YES completion:completion];
5219}
5220
sczsdd860eba2017-08-10 01:55:385221#pragma mark - TabHistoryPresenter
5222
sczs0a726d22017-08-21 22:40:135223- (UIView*)viewForTabHistoryPresentation {
5224 return self.view;
5225}
5226
sczsdd860eba2017-08-10 01:55:385227- (void)prepareForTabHistoryPresentation {
5228 DCHECK(self.visible || self.dismissingModal);
5229 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465230 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385231}
5232
Mike Doughertya1ec26402017-08-23 19:46:315233#pragma mark - CaptivePortalDetectorTabHelperDelegate
5234
5235- (void)captivePortalBlockingPage:(IOSCaptivePortalBlockingPage*)blockingPage
5236 connectWithLandingURL:(GURL)landingURL {
5237 _captivePortalLoginCoordinator = [[CaptivePortalLoginCoordinator alloc]
5238 initWithBaseViewController:self
5239 landingURL:landingURL];
5240 [_captivePortalLoginCoordinator start];
5241}
5242
Gregory Chatzinoffdf93d692017-09-09 01:32:275243#pragma mark - PageInfoPresentation
5244
Gregory Chatzinoffb6a01f72017-09-20 20:06:395245- (void)presentPageInfoView:(UIView*)pageInfoView {
5246 [pageInfoView setFrame:self.view.bounds];
5247 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275248}
5249
5250- (void)prepareForPageInfoPresentation {
5251 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465252 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275253}
5254
Gregory Chatzinoffb6a01f72017-09-20 20:06:395255- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5256 return [self.view convertPoint:origin fromView:nil];
5257}
5258
Sylvain Defresnecacc3a52017-09-12 13:51:045259#pragma mark - WebStatePrinter
5260
5261- (void)printWebState:(web::WebState*)webState {
5262 if (webState == [_model currentTab].webState)
5263 [self printTab];
5264}
5265
Eugene But35ded552017-09-13 23:31:595266#pragma mark - RepostFormTabHelperDelegate
5267
5268- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
5269 presentRepostFromDialogAtPoint:(CGPoint)location
5270 completionHandler:(void (^)(BOOL))completion {
5271 _repostFormCoordinator = [[RepostFormCoordinator alloc]
5272 initWithBaseViewController:self
5273 dialogLocation:location
5274 webState:helper->web_state()
5275 completionHandler:completion];
5276 [_repostFormCoordinator start];
5277}
5278
5279- (void)repostFormTabHelperDismissRepostFormDialog:
5280 (RepostFormTabHelper*)helper {
5281 _repostFormCoordinator = nil;
5282}
5283
edchinf5150c682017-09-18 02:50:035284#pragma mark - TabStripPresentation
5285
5286- (BOOL)isTabStripFullyVisible {
5287 return ([self currentHeaderOffset] == 0.0f);
5288}
5289
5290- (void)showTabStripView:(UIView*)tabStripView {
5291 DCHECK([self isViewLoaded]);
5292 DCHECK(tabStripView);
5293 self.tabStripView = tabStripView;
5294 CGRect tabStripFrame = [self.tabStripView frame];
5295 tabStripFrame.origin = CGPointZero;
5296 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5297 // because the CGPointZero above will break reset the offset, but it's not
5298 // clear what removing that will do.
5299 tabStripFrame.origin.y = [self headerOffset];
5300 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5301 [self.tabStripView setFrame:tabStripFrame];
5302 [[self view] addSubview:tabStripView];
5303}
5304
edchincd32fdf2017-10-25 12:45:455305#pragma mark - ManageAccountsDelegate
5306
5307- (void)onManageAccounts {
5308 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5309 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5310 ->GetState());
5311 [self.dispatcher showAccountsSettings];
5312}
5313
5314- (void)onAddAccount {
5315 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5316 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5317 ->GetState());
5318 [self.dispatcher showAddAccount];
5319}
5320
5321- (void)onGoIncognito:(const GURL&)url {
5322 // The user taps on go incognito from the mobile U-turn webpage (the web page
5323 // that displays all users accounts available in the content area). As the
5324 // user chooses to go to incognito, the mobile U-turn page is no longer
5325 // neeeded. The current solution is to go back in history. This has the
5326 // advantage of keeping the current browsing session and give a good user
5327 // experience when the user comes back from incognito.
5328 [self.tabModel.currentTab goBack];
5329
5330 if (url.is_valid()) {
5331 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5332 initWithURL:url
5333 referrer:web::Referrer() // Strip referrer when switching modes.
5334 inIncognito:YES
5335 inBackground:NO
5336 appendTo:kLastTab];
5337 [self.dispatcher openURL:command];
5338 } else {
5339 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5340 }
5341}
5342
sdefresnee65fd872016-12-19 13:38:135343@end