blob: 0e10aaf95f84bb4bbb42d7a44d33e76cee59f581 [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"
138#import "ios/chrome/browser/ui/fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38139#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13140#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13141#import "ios/chrome/browser/ui/key_commands_provider.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10142#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13143#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42144#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13145#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27146#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
147#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13148#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37149#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13150#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11151#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
152#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13153#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03154#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13155#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13156#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53157#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39158#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin7f210cd2017-09-28 08:03:53160#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13161#import "ios/chrome/browser/ui/stack_view/card_view.h"
162#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
163#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13164#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03165#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
166#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
167#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
Jean-François Geyelined4cde72017-10-11 11:34:50168#import "ios/chrome/browser/ui/toolbar/toolbar_controller_base_feature.h"
sczsf1620e52017-10-02 22:54:46169#include "ios/chrome/browser/ui/toolbar/toolbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13170#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
171#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sczsc2b4f152017-10-11 01:44:24172#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
sczsbbad1632017-07-29 03:48:00173#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13174#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
175#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
176#include "ios/chrome/browser/ui/ui_util.h"
177#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28178#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13179#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
180#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51181#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13182#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27183#import "ios/chrome/browser/web/load_timing_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13184#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04185#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05186#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59187#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53188#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04189#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05190#import "ios/chrome/browser/web_state_list/web_state_list.h"
191#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57192#import "ios/chrome/browser/webui/net_export_tab_helper.h"
193#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
194#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13195#include "ios/chrome/grit/ios_chromium_strings.h"
196#include "ios/chrome/grit/ios_strings.h"
197#import "ios/net/request_tracker.h"
198#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
199#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
200#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
201#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
202#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
203#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
204#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
205#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36206#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13207#include "ios/web/public/navigation_item.h"
208#import "ios/web/public/navigation_manager.h"
209#include "ios/web/public/referrer_util.h"
210#include "ios/web/public/ssl_status.h"
211#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39212#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13213#include "ios/web/public/web_client.h"
214#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13215#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29216#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19217#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13218#import "ios/web/public/web_state/web_state_delegate_bridge.h"
219#include "ios/web/public/web_thread.h"
220#import "ios/web/web_state/ui/crw_web_controller.h"
221#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17222#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13223#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
224#include "net/ssl/ssl_info.h"
225#include "net/url_request/url_request_context_getter.h"
226#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
227#include "ui/base/l10n/l10n_util.h"
228#include "ui/base/l10n/l10n_util_mac.h"
229#include "ui/base/page_transition_types.h"
230#include "url/gurl.h"
231
stkhapuginf58b10d02017-04-10 13:36:17232#if !defined(__has_feature) || !__has_feature(objc_arc)
233#error "This file requires ARC support."
234#endif
235
sdefresnee65fd872016-12-19 13:38:13236using base::UserMetricsAction;
237using bookmarks::BookmarkNode;
238
239class BrowserBookmarkModelBridge;
240class InfoBarContainerDelegateIOS;
241
sdefresnee65fd872016-12-19 13:38:13242NSString* const kLocationBarBecomesFirstResponderNotification =
243 @"kLocationBarBecomesFirstResponderNotification";
244NSString* const kLocationBarResignsFirstResponderNotification =
245 @"kLocationBarResignsFirstResponderNotification";
sdefresnee65fd872016-12-19 13:38:13246
247namespace {
248
249typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
250 // Note: these values must match the ContextMenuOption enum in histograms.xml.
251 ACTION_OPEN_IN_NEW_TAB = 0,
252 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
253 ACTION_COPY_LINK_ADDRESS = 2,
254 ACTION_SAVE_IMAGE = 6,
255 ACTION_OPEN_IMAGE = 7,
256 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
257 ACTION_SEARCH_BY_IMAGE = 11,
258 ACTION_OPEN_JAVASCRIPT = 21,
259 ACTION_READ_LATER = 22,
260 NUM_ACTIONS = 23,
261};
262
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28263void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13264 if (is_image) {
265 if (is_link) {
266 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
267 NUM_ACTIONS);
268 } else {
269 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
270 NUM_ACTIONS);
271 }
272 } else {
273 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
274 NUM_ACTIONS);
275 }
276}
277
edchinf5150c682017-09-18 02:50:03278// Returns the status bar background color.
279UIColor* StatusBarBackgroundColor() {
280 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
281}
282
283// Duration of the toolbar animation.
284const NSTimeInterval kFullScreenControllerToolbarAnimationDuration = 0.3;
285
sdefresnee65fd872016-12-19 13:38:13286const CGFloat kVoiceSearchBarHeight = 59.0;
287
288// Dimensions to use when downsizing an image for search-by-image.
289const CGFloat kSearchByImageMaxImageArea = 90000.0;
290const CGFloat kSearchByImageMaxImageWidth = 600.0;
291const CGFloat kSearchByImageMaxImageHeight = 400.0;
292
sdefresnee65fd872016-12-19 13:38:13293enum HeaderBehaviour {
294 // The header moves completely out of the screen.
295 Hideable = 0,
296 // This header stays on screen and doesn't overlap with the content.
297 Visible,
298 // This header stay on screen and covers part of the content.
299 Overlap
300};
301
sdefresnee65fd872016-12-19 13:38:13302const CGFloat kIPadFindBarOverlap = 11;
303
304bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24305 // Most URLs are allowed in incognito; the following is an exception.
306 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13307}
308
edchineeb4d422017-10-02 17:39:36309// Snackbar category for browser view controller.
310NSString* const kBrowserViewControllerSnackbarCategory =
311 @"BrowserViewControllerSnackbarCategory";
312
rohitrao005a6432017-03-16 20:52:42313} // namespace
sdefresnee65fd872016-12-19 13:38:13314
stkhapugin952ecef2017-04-11 12:11:45315#pragma mark - HeaderDefinition helper
316
317@interface HeaderDefinition : NSObject
318
319// The header view.
320@property(nonatomic, strong) UIView* view;
321// How to place the view, and its behaviour when the headers move.
322@property(nonatomic, assign) HeaderBehaviour behaviour;
323// Reduces the height of a header to adjust for shadows.
324@property(nonatomic, assign) CGFloat heightAdjustement;
325// Nudges that particular header up by this number of points.
326@property(nonatomic, assign) CGFloat inset;
327
328- (instancetype)initWithView:(UIView*)view
329 headerBehaviour:(HeaderBehaviour)behaviour
330 heightAdjustment:(CGFloat)heightAdjustment
331 inset:(CGFloat)inset;
332
333+ (instancetype)definitionWithView:(UIView*)view
334 headerBehaviour:(HeaderBehaviour)behaviour
335 heightAdjustment:(CGFloat)heightAdjustment
336 inset:(CGFloat)inset;
337
338@end
339
340@implementation HeaderDefinition
341@synthesize view = _view;
342@synthesize behaviour = _behaviour;
343@synthesize heightAdjustement = _heightAdjustement;
344@synthesize inset = _inset;
345
346+ (instancetype)definitionWithView:(UIView*)view
347 headerBehaviour:(HeaderBehaviour)behaviour
348 heightAdjustment:(CGFloat)heightAdjustment
349 inset:(CGFloat)inset {
350 return [[self alloc] initWithView:view
351 headerBehaviour:behaviour
352 heightAdjustment:heightAdjustment
353 inset:inset];
354}
355
356- (instancetype)initWithView:(UIView*)view
357 headerBehaviour:(HeaderBehaviour)behaviour
358 heightAdjustment:(CGFloat)heightAdjustment
359 inset:(CGFloat)inset {
360 self = [super init];
361 if (self) {
362 _view = view;
363 _behaviour = behaviour;
364 _heightAdjustement = heightAdjustment;
365 _inset = inset;
366 }
367 return self;
368}
369
370@end
371
372#pragma mark - BVC
373
Rohit Rao01e0e002017-08-14 20:49:43374@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43375 AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13376 CRWNativeContentProvider,
377 CRWWebStateDelegate,
378 DialogPresenterDelegate,
379 FullScreenControllerDelegate,
Mike Doughertya1ec26402017-08-23 19:46:31380 IOSCaptivePortalBlockingPageDelegate,
sdefresnee65fd872016-12-19 13:38:13381 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57382 NetExportTabHelperDelegate,
edchincd32fdf2017-10-25 12:45:45383 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13384 MFMailComposeViewControllerDelegate,
385 NewTabPageControllerObserver,
386 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27387 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13388 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55389 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13390 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11391 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59392 RepostFormTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13393 SKStoreProductViewControllerDelegate,
394 SnapshotOverlayProvider,
395 StoreKitLauncher,
396 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19397 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38398 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13399 TabModelObserver,
400 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03401 TabStripPresentation,
sdefresnee65fd872016-12-19 13:38:13402 UIGestureRecognizerDelegate,
403 UpgradeCenterClientProtocol,
404 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04405 VoiceSearchBarOwner,
406 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13407 // The dependency factory passed on initialization. Used to vend objects used
408 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27409 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13410
411 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27412 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13413
sczsf1620e52017-10-02 22:54:46414 // Facade objects used by |_toolbarCoordinator|.
415 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13416 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
417 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
418
sdefresnee65fd872016-12-19 13:38:13419 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27420 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13421
Mike Doughertya1ec26402017-08-23 19:46:31422 // Handles displaying the captive portal login page.
423 CaptivePortalLoginCoordinator* _captivePortalLoginCoordinator;
424
sdefresnee65fd872016-12-19 13:38:13425 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27426 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13427
428 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27429 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13430
431 // Handles presentation of JavaScript dialogs.
432 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
433
justincohen75011c32017-04-28 16:31:39434 // Handles command dispatching.
435 CommandDispatcher* _dispatcher;
436
sdefresnee65fd872016-12-19 13:38:13437 // Keyboard commands provider. It offloads most of the keyboard commands
438 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27439 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13440
441 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
442 // to |-reparentToolbarController| will reset it to NO.
443 BOOL _isToolbarControllerRelinquished;
444
445 // The controller that owns the currently relinquished toolbar controller.
446 // The reference is weak because it's possible for the toolbar owner to be
447 // deallocated mid-animation due to memory pressure or a tab being closed
448 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27449 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13450
sdefresnee65fd872016-12-19 13:38:13451 // Used to inject Javascript implementing the PaymentRequest API and to
452 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27453 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13454
sdefresnee65fd872016-12-19 13:38:13455 // Used to display the Voice Search UI. Nil if not visible.
456 scoped_refptr<VoiceSearchController> _voiceSearchController;
457
gambard6299cc1d2017-02-21 13:06:03458 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27459 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03460
sdefresnee65fd872016-12-19 13:38:13461 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27462 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13463
sdefresnee65fd872016-12-19 13:38:13464 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27465 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13466
467 // Records the set of domains for which full screen alert has already been
468 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27469 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13470
471 // Adapter to let BVC be the delegate for WebState.
472 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
473
474 // YES if new tab is animating in.
475 BOOL _inNewTabAnimation;
476
477 // YES if Voice Search should be started when the new tab animation is
478 // finished.
479 BOOL _startVoiceSearchAfterNewTabAnimation;
480
481 // YES if the user interacts with the location bar.
482 BOOL _locationBarHasFocus;
483 // YES if a load was cancelled due to typing in the location bar.
484 BOOL _locationBarEditCancelledLoad;
485 // YES if waiting for a foreground tab due to expectNewForegroundTab.
486 BOOL _expectingForegroundTab;
487
Sylvain Defresne41170aa2017-06-15 10:25:20488 // Whether or not -shutdown has been called.
489 BOOL _isShutdown;
490
sdefresnee65fd872016-12-19 13:38:13491 // The ChromeBrowserState associated with this BVC.
492 ios::ChromeBrowserState* _browserState; // weak
493
494 // Whether or not Incognito* is enabled.
495 BOOL _isOffTheRecord;
496
497 // The last point within |_contentArea| that's received a touch.
498 CGPoint _lastTapPoint;
499
500 // The time at which |_lastTapPoint| was most recently set.
501 CFTimeInterval _lastTapTime;
502
503 // A single infobar container handles all infobars in all tabs. It keeps
504 // track of infobars for current tab (accessed via infobar helper of
505 // the current tab).
506 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
507
508 // Bridge class to deliver container change notifications to BVC.
509 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
510
511 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37512 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27513 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13514
515 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11516 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13517
sdefresnee65fd872016-12-19 13:38:13518 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27519 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13520
521 // Bridge to register for bookmark changes.
522 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
523
524 // Cached pointer to the bookmarks model.
525 bookmarks::BookmarkModel* _bookmarkModel; // weak
526
527 // The controller that shows the bookmarking UI after the user taps the star
528 // button.
stkhapuginc9eee7b2017-04-10 15:49:27529 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13530
sdefresnee65fd872016-12-19 13:38:13531 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27532 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13533
Eugene But56efc322017-08-11 14:03:44534 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28535 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13536
537 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27538 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13539
Jean-François Geyelin3d47c212017-08-03 09:24:09540 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27541 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13542
Rohit Rao01e0e002017-08-14 20:49:43543 // Coordinator for the share menu (Activity Services).
544 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
545
sdefresnee65fd872016-12-19 13:38:13546 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27547 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38548
Rohit Raocda0a992017-08-16 15:37:11549 // Coordinator for the QR scanner.
550 QRScannerLegacyCoordinator* _qrScannerCoordinator;
551
sczsdd860eba2017-08-10 01:55:38552 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13553 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53554
555 // Coordinator for displaying Sad Tab.
556 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27557
558 // Coordinator for Page Info UI.
559 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59560
561 // Coordinator for displaying Repost Form dialog.
562 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22563
edchin7f210cd2017-09-28 08:03:53564 // Coordinator for displaying snackbars.
565 SnackbarCoordinator* _snackbarCoordinator;
566
sczsf1620e52017-10-02 22:54:46567 // Coordinator for the toolbar.
568 LegacyToolbarCoordinator* _toolbarCoordinator;
569
Louis Romerod11747a2017-10-20 20:10:35570 // Coordinator for the External Search UI.
571 ExternalSearchCoordinator* _externalSearchCoordinator;
572
Justin Cohenb3170c32017-09-19 01:55:22573 // Fake status bar view used to blend the toolbar into the status bar.
574 UIView* _fakeStatusBarView;
sdefresnee65fd872016-12-19 13:38:13575}
576
577// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17578@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13579// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17580@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13581// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17582@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13583// Whether the current tab can enable the request desktop menu item.
584@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
585// Whether the sharing menu should be enabled.
586@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
587// Helper method to check web controller canShowFindBar method.
588@property(nonatomic, assign, readonly) BOOL canShowFindBar;
589// Whether the controller's view is currently available.
590// YES from viewWillAppear to viewWillDisappear.
591@property(nonatomic, assign, getter=isVisible) BOOL visible;
592// Whether the controller's view is currently visible.
593// YES from viewDidAppear to viewWillDisappear.
594@property(nonatomic, assign) BOOL viewVisible;
595// Whether the controller is currently dismissing a presented view controller.
596@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
597// Returns YES if the toolbar has not been scrolled out by fullscreen.
598@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
599 BOOL toolbarOnScreen;
600// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12601@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13602// Whether BVC prefers to hide the status bar. This value is used to determine
603// the response from the |prefersStatusBarHidden| method.
604@property(nonatomic, assign) BOOL hideStatusBar;
605// Whether the VoiceSearchBar should be displayed.
606@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
607// Coordinator for displaying a modal overlay with activity indicator to prevent
608// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17609@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13610 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39611// A block to be run when the |tabWasAdded:| method completes the animation
612// for the presentation of a new tab. Can be used to record performance metrics.
613@property(nonatomic, strong, nullable)
614 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42615// Coordinator for Recent Tabs.
616@property(nonatomic, strong)
617 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03618// Coordinator for tablet tab strip.
619@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
620// A weak reference to the view of the tab strip on tablet.
621@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13622
liaoyukeea9f3ee62017-03-07 22:05:39623// The user agent type used to load the currently visible page. User agent type
624// is NONE if there is no visible page or visible page is a native page.
625@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
626
stkhapugin952ecef2017-04-11 12:11:45627// Returns the header views, all the chrome on top of the page, including the
628// ones that cannot be scrolled off screen by full screen.
629@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
630
Cooper Knaakd0a974cd2017-08-10 18:05:47631// Used to display the new tab tip in-product help promotion bubble. |nil| if
632// the new tab tip bubble has not yet been presented. Once the bubble is
633// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38634@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47635 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38636
Helen Yang9175bd52017-08-12 00:28:40637// Used to display the new incognito tab tip in-product help promotion bubble.
638@property(nonatomic, strong)
639 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
640
sdefresnee65fd872016-12-19 13:38:13641// BVC initialization:
642// If the BVC is initialized with a valid browser state & tab model immediately,
643// the path is straightforward: functionality is enabled, and the UI is built
644// when -viewDidLoad is called.
645// If the BVC is initialized without a browser state or tab model, the tab model
646// and browser state may or may not be provided before -viewDidLoad is called.
647// In most cases, they will not, to improve startup performance.
648// In order to handle this, initialization of various aspects of BVC have been
649// broken out into the following functions, which have expectations (enforced
650// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
651
652// Registers for notifications.
653- (void)registerForNotifications;
654// Called when a tab is starting to load. If it's a link click or form
655// submission, the user is navigating away from any entries in the forward
656// history. Tell the toolbar so it can update the UI appropriately.
657// See the warning on [Tab webWillStartLoadingURL] about invocation of this
658// method sequence by malicious pages.
659- (void)pageLoadStarting:(NSNotification*)notify;
660// Called when a tab actually starts loading.
661- (void)pageLoadStarted:(NSNotification*)notify;
662// Called when a tab finishes loading. Update the Omnibox with the url and
663// stop any page load progess display.
664- (void)pageLoadComplete:(NSNotification*)notify;
665// Called when a tab is deselected in the model.
666// This notification also occurs when a tab is closed.
667- (void)tabDeselected:(NSNotification*)notify;
668// Animates sliding current tab and rotate-entering new tab while new tab loads
669// in background on the iPhone only.
670- (void)tabWasAdded:(NSNotification*)notify;
671
672// Updates non-view-related functionality with the given browser state and tab
673// model.
674// Does not matter whether or not the view has been loaded.
675- (void)updateWithTabModel:(TabModel*)model
676 browserState:(ios::ChromeBrowserState*)browserState;
677// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
678// the status bar to mimic this layout.
679- (void)installFakeStatusBar;
680// Builds the UI parts of tab strip and the toolbar. Does not matter whether
681// or not browser state and tab model are valid.
682- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50683// Sets up the constraints on the toolbar.
684- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13685// Updates view-related functionality with the given tab model and browser
686// state. The view must have been loaded. Uses |_browserState| and |_model|.
687- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22688// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13689- (void)setUpViewLayout;
sdefresnee65fd872016-12-19 13:38:13690// Makes |tab| the currently visible tab, displaying its view. Calls
691// -selectedTabChanged on the toolbar only if |newSelection| is YES.
692- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
693// Initializes the bookmark interaction controller if not already initialized.
694- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13695// Add all delegates to the provided |tab|.
696- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14697// Remove delegates from the provided |tab|.
698- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13699// Closes the current tab, with animation if applicable.
700- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13701// Show the bookmarks page.
702- (void)showAllBookmarks;
703// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23704- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13705// Dismisses the "rate this app" dialog.
706- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32707// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13708- (BOOL)isTabNativePage:(Tab*)tab;
709// Returns the view to use when animating a page in or out, positioning it to
710// fill the content area but not actually adding it to the view hierarchy.
711- (UIImageView*)pageOpenCloseAnimationView;
712// Returns the view to use when animating full screen NTP paper in, filling the
713// entire screen but not actually adding it to the view hierarchy.
714- (UIImageView*)pageFullScreenOpenCloseAnimationView;
715// Updates the toolbar display based on the current tab.
716- (void)updateToolbar;
717// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12718// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13719- (void)updateDialogPresenterActiveState;
720// Dismisses popups and modal dialogs that are displayed above the BVC upon size
721// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
722// is performed.
723// TODO(crbug.com/522721): Support size changes for all popups and modal
724// dialogs.
725- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47726
727// Returns a bubble associated with an in-product help promotion if
728// it is valid to show the promotion and |nil| otherwise. |feature| is the
729// base::Feature object associated with the given promotion. |direction| is the
730// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21731// arrow on the button. |text| is the text displayed by the bubble. This method
732// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47733- (BubbleViewControllerPresenter*)
734bubblePresenterForFeature:(const base::Feature&)feature
735 direction:(BubbleArrowDirection)direction
736 alignment:(BubbleAlignment)alignment
737 text:(NSString*)text;
738
Cooper Knaak120cee5e2017-08-10 20:57:00739// Waits to present a bubble associated with the new tab tip in-product help
740// promotion until the feature engagement tracker database is fully initialized.
741// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
742// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11743// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21744// determines it is not valid to present it. This method requires that
745// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00746- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11747// Optionally presents a bubble associated with the new tab tip in-product help
748// promotion. If the feature engagement tracker determines it is valid to show
749// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
750// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21751// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
752// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00753- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40754// Waits to present a bubble associated with the new incognito tab tip
755// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21756// fully initialized. This method requires that |self.browserState| is
757// not NULL.
Helen Yang9175bd52017-08-12 00:28:40758- (void)presentNewIncognitoTabTipBubbleOnInitialized;
759// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21760// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40761- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21762// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
763// eligible. Only one can be eligible per session (as enforced by the
764// FeatureEngagementTracker). If neither is eligible, neither bubble is
765// presented. This method requires that |self.browserState| is not NULL.
766- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00767
sdefresnee65fd872016-12-19 13:38:13768// Update find bar with model data. If |shouldFocus| is set to YES, the text
769// field will become first responder.
770- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13771// Hide find bar.
772- (void)hideFindBarWithAnimation:(BOOL)animate;
773// Shows find bar. If |selectText| is YES, all text inside the Find Bar
774// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
775// set to be first responder.
776- (void)showFindBarWithAnimation:(BOOL)animate
777 selectText:(BOOL)selectText
778 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36779
sdefresnee65fd872016-12-19 13:38:13780// The infobar state (typically height) has changed.
781- (void)infoBarContainerStateChanged:(bool)is_animating;
782// Adds a CardView on top of the contentArea either taking the size of the full
783// screen or just the size of the space under the header.
784// Returns the CardView that was added.
785- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
786// Called when either a tab finishes loading or when a tab with finished content
787// is added directly to the model via pre-rendering. The tab must be non-nil and
788// must be a member of the tab model controlled by this BrowserViewController.
789- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
790// Evaluates Javascript asynchronously using the current page context.
791- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36792// Shows a self-dismissing snackbar displaying |message|.
793- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13794// Induces an intentional crash in the browser process.
795- (void)induceBrowserCrash;
796// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17797- (void)managePermissionAndSaveImage:(NSData*)data
798 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13799// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24800// saved as a temporary file on disk then saved in photos. Saving will happen
801// on a background sequence and the completion block will be invoked on that
802// sequence.
803- (void)saveImage:(NSData*)data
804 withFileExtension:(NSString*)fileExtension
805 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13806// Called when Chrome has been denied access to the photos or videos and the
807// user can change it.
808// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
809// settings. Dismiss previous alert if it has not been dismissed yet.
810- (void)displayImageErrorAlertWithSettingsOnMainQueue;
811// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
812// previous alert if it has not been dismissed yet.
813- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
814// Called when Chrome has been denied access to the photos or videos and the
815// user cannot change it.
816// Shows a privacy alert on the main queue, with errorContent as the message.
817// Dismisses previous alert if it has not been dismissed yet.
818- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
819// Called with the results of saving a picture in the photo album. If error is
820// nil the save succeeded.
821- (void)finishSavingImageWithError:(NSError*)error;
822// Provides a view that encompasses currently displayed infobar(s) or nil
823// if no infobar is presented.
824- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
825// Returns a vertical infobar offset relative to the tab content.
826- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
827// Provides a view that encompasses the voice search bar if it's displayed or
828// nil if the voice search bar isn't displayed.
829- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
830// Returns a vertical voice search bar offset relative to the tab content.
831- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
832// Lazily instantiates |_voiceSearchController|.
833- (void)ensureVoiceSearchControllerCreated;
834// Lazily instantiates |_voiceSearchBar| and adds it to the view.
835- (void)ensureVoiceSearchBarCreated;
836// Shows/hides the voice search bar.
837- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
838// The LogoAnimationControllerOwner to be used for the next logo transition
839// animation.
840- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13841// Returns the footer view if one exists (e.g. the voice search bar).
842- (UIView*)footerView;
843// Returns the height of the header view for the tab model's current tab.
844- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13845// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45846- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13847 atOffset:(CGFloat)headerOffset;
848// Returns the y coordinate for the footer's frame when animating the footer
849// in/out of fullscreen.
850- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
851// Called when the animation for setting the header view's offset is finished.
852// |completed| should indicate if the animation finished completely or was
853// interrupted. |offset| should indicate the header offset after the animation.
854// |dragged| should indicate if the header moved due to the user dragging.
855- (void)fullScreenController:(FullScreenController*)controller
856 headerAnimationCompleted:(BOOL)completed
857 offset:(CGFloat)offset;
858// Performs a search with the image at the given url. The referrer is used to
859// download the image.
860- (void)searchByImageAtURL:(const GURL&)url
861 referrer:(const web::Referrer)referrer;
862// Saves the image at the given URL on the system's album. The referrer is used
863// to download the image.
864- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
865
Mark Cogandfcdea72017-07-18 13:47:38866// Record the last tap point based on the |originPoint| (if any) passed in
867// |command|.
868- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13869// Get return the last stored |_lastTapPoint| if it's been set within the past
870// second.
871- (CGPoint)lastTapPoint;
872// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
873- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
874// Returns the native controller being used by |tab|'s web controller.
875- (id)nativeControllerForTab:(Tab*)tab;
876// Installs the BVC as overscroll actions controller of |nativeContent| if
877// needed. Sets the style of the overscroll actions toolbar.
878- (void)setOverScrollActionControllerToStaticNativeContent:
879 (StaticHtmlNativeContent*)nativeContent;
880// Whether the BVC should declare keyboard commands.
881- (BOOL)shouldRegisterKeyboardCommands;
882// Adds the given url to the reading list.
883- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
884@end
885
886class InfoBarContainerDelegateIOS
887 : public infobars::InfoBarContainer::Delegate {
888 public:
889 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
890 : controller_(controller) {}
891
892 ~InfoBarContainerDelegateIOS() override {}
893
894 private:
895 SkColor GetInfoBarSeparatorColor() const override {
896 NOTIMPLEMENTED();
897 return SK_ColorBLACK;
898 }
899
900 int ArrowTargetHeightForInfoBar(
901 size_t index,
902 const gfx::SlideAnimation& animation) const override {
903 return 0;
904 }
905
906 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
907 int arrow_target_height,
908 int bar_target_height,
909 int* arrow_height,
910 int* arrow_half_width,
911 int* bar_height) const override {
912 DCHECK_NE(-1, bar_target_height)
913 << "Infobars don't have a default height on iOS";
914 *arrow_height = 0;
915 *arrow_half_width = 0;
916 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
917 }
918
919 void InfoBarContainerStateChanged(bool is_animating) override {
920 [controller_ infoBarContainerStateChanged:is_animating];
921 }
922
923 bool DrawInfoBarArrows(int* x) const override { return false; }
924
stkhapuginf58b10d02017-04-10 13:36:17925 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13926};
927
928// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
929@interface BrowserViewController (BookmarkBridgeMethods)
930// If a bookmark matching the currentTab url is added or moved, update the
931// toolbar state so the star highlight is in sync.
932- (void)bookmarkNodeModified:(const BookmarkNode*)node;
933- (void)allBookmarksRemoved;
934@end
935
936// Handle notification that bookmarks has been removed changed so we can update
937// the bookmarked star icon.
938class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
939 public:
940 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
941 : owner_(owner) {}
942
943 ~BrowserBookmarkModelBridge() override {}
944
945 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
946 const BookmarkNode* parent,
947 int old_index,
948 const BookmarkNode* node,
949 const std::set<GURL>& removed_urls) override {
950 [owner_ bookmarkNodeModified:node];
951 }
952
953 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
954 bool ids_reassigned) override {}
955
956 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
957 const BookmarkNode* old_parent,
958 int old_index,
959 const BookmarkNode* new_parent,
960 int new_index) override {}
961
962 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
963 const BookmarkNode* parent,
964 int index) override {
965 [owner_ bookmarkNodeModified:parent->GetChild(index)];
966 }
967
968 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
969 const BookmarkNode* node) override {}
970
971 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
972 const BookmarkNode* node) override {}
973
974 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
975 const BookmarkNode* node) override {}
976
977 void BookmarkAllUserNodesRemoved(
978 bookmarks::BookmarkModel* model,
979 const std::set<GURL>& removed_urls) override {
980 [owner_ allBookmarksRemoved];
981 }
982
983 private:
stkhapuginf58b10d02017-04-10 13:36:17984 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13985};
986
987@implementation BrowserViewController
988
989@synthesize contentArea = _contentArea;
990@synthesize typingShield = _typingShield;
991@synthesize active = _active;
992@synthesize visible = _visible;
993@synthesize viewVisible = _viewVisible;
994@synthesize dismissingModal = _dismissingModal;
995@synthesize hideStatusBar = _hideStatusBar;
996@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
997@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39998@synthesize foregroundTabWasAddedCompletionBlock =
999 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:401000@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
1001@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:421002@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:031003@synthesize tabStripCoordinator = _tabStripCoordinator;
1004@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:131005
1006#pragma mark - Object lifecycle
1007
Mark Cogan5e3da152017-07-11 15:57:301008- (instancetype)
1009 initWithTabModel:(TabModel*)model
1010 browserState:(ios::ChromeBrowserState*)browserState
1011 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
1012applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:131013 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
1014 if (self) {
1015 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:171016
stkhapuginc9eee7b2017-04-10 15:49:271017 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:271018 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
1019 presentingViewController:self];
justincohen75011c32017-04-28 16:31:391020 _dispatcher = [[CommandDispatcher alloc] init];
1021 [_dispatcher startDispatchingToTarget:self
1022 forProtocol:@protocol(UrlLoader)];
1023 [_dispatcher startDispatchingToTarget:self
1024 forProtocol:@protocol(WebToolbarDelegate)];
1025 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241026 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301027 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1028 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321029 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1030 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1031 // dispatched to the endpoint as well. Since this is potentially
1032 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1033 DCHECK(!applicationCommandEndpoint ||
1034 [applicationCommandEndpoint
1035 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1036 [_dispatcher
1037 startDispatchingToTarget:applicationCommandEndpoint
1038 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391039
edchin7f210cd2017-09-28 08:03:531040 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
1041 _snackbarCoordinator.dispatcher = _dispatcher;
1042 [_snackbarCoordinator start];
1043
sdefresnee65fd872016-12-19 13:38:131044 _javaScriptDialogPresenter.reset(
1045 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1046 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1047 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151048 [[UpgradeCenter sharedInstance] registerClient:self
1049 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131050 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131051 if (model && browserState)
1052 [self updateWithTabModel:model browserState:browserState];
1053 if ([[NSUserDefaults standardUserDefaults]
1054 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271055 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131056 }
1057 }
1058 return self;
1059}
1060
1061- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1062 bundle:(NSBundle*)nibBundleOrNil {
1063 NOTREACHED();
1064 return nil;
1065}
1066
1067- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1068 NOTREACHED();
1069 return nil;
1070}
1071
1072- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201073 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131074}
1075
1076#pragma mark - Accessibility
1077
1078- (BOOL)accessibilityPerformEscape {
1079 [self dismissPopups];
1080 return YES;
1081}
1082
1083#pragma mark - Properties
1084
edchin3365c7d2017-09-01 22:20:371085- (id<ApplicationCommands,
1086 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371087 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531088 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371089 UrlLoader,
1090 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561091 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531092 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1093 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241094}
1095
sdefresnee65fd872016-12-19 13:38:131096- (void)setActive:(BOOL)active {
1097 if (_active == active) {
1098 return;
1099 }
1100 _active = active;
1101
1102 // If not active, display an activity indicator overlay over the view to
1103 // prevent interaction with the web page.
1104 // TODO(crbug.com/637093): This coordinator should be managed by the
1105 // coordinator used to present BrowserViewController, when implemented.
1106 if (active) {
1107 [self.activityOverlayCoordinator stop];
1108 self.activityOverlayCoordinator = nil;
1109 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171110 self.activityOverlayCoordinator =
1111 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131112 [self.activityOverlayCoordinator start];
1113 }
1114
1115 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091116 ActiveStateManager* active_state_manager =
1117 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131118 active_state_manager->SetActive(active);
1119 }
1120
1121 [_model setWebUsageEnabled:active];
1122 [self updateDialogPresenterActiveState];
1123
1124 if (active) {
1125 // Make sure the tab (if any; it's possible to get here without a current
1126 // tab if the caller is about to create one) ends up on screen completely.
1127 Tab* currentTab = [_model currentTab];
1128 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441129 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131130 if (_expectingForegroundTab)
1131 [currentTab.webController setOverlayPreviewMode:YES];
1132 if (currentTab)
1133 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341134 } else {
1135 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131136 }
sdefresnee65fd872016-12-19 13:38:131137 [_paymentRequestManager enablePaymentRequest:active];
1138
1139 [self setNeedsStatusBarAppearanceUpdate];
1140}
1141
1142- (void)setPrimary:(BOOL)primary {
1143 [_model setPrimary:primary];
1144 if (primary) {
1145 [self updateDialogPresenterActiveState];
1146 } else {
1147 self.dialogPresenter.active = false;
1148 }
1149}
1150
1151- (BOOL)isPlayingTTS {
1152 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1153}
1154
sdefresne6165c8742017-01-16 15:42:021155- (ios::ChromeBrowserState*)browserState {
1156 return _browserState;
1157}
1158
1159- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271160 return _model;
sdefresne6165c8742017-01-16 15:42:021161}
1162
sdefresnee65fd872016-12-19 13:38:131163- (SideSwipeController*)sideSwipeController {
1164 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271165 _sideSwipeController =
1166 [[SideSwipeController alloc] initWithTabModel:_model
1167 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131168 [_sideSwipeController setSnapshotDelegate:self];
1169 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031170 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131171 }
1172 return _sideSwipeController;
1173}
1174
sdefresnee65fd872016-12-19 13:38:131175- (DialogPresenter*)dialogPresenter {
1176 return _dialogPresenter;
1177}
1178
sdefresnee65fd872016-12-19 13:38:131179- (BOOL)canUseDesktopUserAgent {
1180 Tab* tab = [_model currentTab];
1181 if ([self isTabNativePage:tab])
1182 return NO;
1183
1184 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441185 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131186}
1187
1188// Whether the sharing menu should be shown.
1189- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191190 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251191 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131192}
1193
1194- (BOOL)canShowFindBar {
1195 // Make sure web controller can handle find in page.
1196 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421197 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131198 return NO;
rohitrao005a6432017-03-16 20:52:421199 }
sdefresnee65fd872016-12-19 13:38:131200
rohitrao005a6432017-03-16 20:52:421201 auto* helper = FindTabHelper::FromWebState(tab.webState);
1202 return (helper && helper->CurrentPageSupportsFindInPage() &&
1203 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131204}
1205
liaoyukeea9f3ee62017-03-07 22:05:391206- (web::UserAgentType)userAgentType {
1207 web::WebState* webState = [_model currentTab].webState;
1208 if (!webState)
1209 return web::UserAgentType::NONE;
1210 web::NavigationItem* visibleItem =
1211 webState->GetNavigationManager()->GetVisibleItem();
1212 if (!visibleItem)
1213 return web::UserAgentType::NONE;
1214
1215 return visibleItem->GetUserAgentType();
1216}
1217
sdefresnee65fd872016-12-19 13:38:131218- (void)setVisible:(BOOL)visible {
1219 if (_visible == visible)
1220 return;
1221 _visible = visible;
1222}
1223
1224- (void)setViewVisible:(BOOL)viewVisible {
1225 if (_viewVisible == viewVisible)
1226 return;
1227 _viewVisible = viewVisible;
1228 self.visible = viewVisible;
1229 [self updateDialogPresenterActiveState];
1230}
1231
1232- (BOOL)isToolbarOnScreen {
1233 return [self headerHeight] - [self currentHeaderOffset] > 0;
1234}
1235
kkhorimotoa44349c12017-04-12 23:02:121236- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1237 if (_inNewTabAnimation == inNewTabAnimation)
1238 return;
1239 _inNewTabAnimation = inNewTabAnimation;
1240 [self updateDialogPresenterActiveState];
1241}
1242
sdefresnee65fd872016-12-19 13:38:131243- (BOOL)isInNewTabAnimation {
1244 return _inNewTabAnimation;
1245}
1246
1247- (BOOL)shouldShowVoiceSearchBar {
1248 // On iPads, the voice search bar should only be shown for regular horizontal
1249 // size class configurations. It should always be shown for voice search
1250 // results Tabs on iPhones, including configurations with regular horizontal
1251 // size classes (i.e. landscape iPhone 6 Plus).
1252 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1253 UIUserInterfaceSizeClassCompact;
1254 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1255 (!IsIPadIdiom() || compactWidth);
1256}
1257
1258- (void)setHideStatusBar:(BOOL)hideStatusBar {
1259 if (_hideStatusBar == hideStatusBar)
1260 return;
1261 _hideStatusBar = hideStatusBar;
1262 [self setNeedsStatusBarAppearanceUpdate];
1263}
1264
1265#pragma mark - IBActions
1266
1267- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461268 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131269}
1270
Cooper Knaakd0a974cd2017-08-10 18:05:471271- (void)userEnteredTabSwitcher {
1272 if ([self.tabTipBubblePresenter isUserEngaged]) {
1273 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1274 }
1275}
1276
Cooper Knaake963d6702017-08-11 21:03:111277- (void)presentBubblesIfEligible {
1278 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401279 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111280}
1281
sdefresnee65fd872016-12-19 13:38:131282#pragma mark - UIViewController methods
1283
1284// Perform additional set up after loading the view, typically from a nib.
1285- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091286 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281287 initialViewsRect.origin.y += StatusBarHeight();
1288 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131289 UIViewAutoresizing initialViewAutoresizing =
1290 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1291
stkhapuginf58b10d02017-04-10 13:36:171292 self.contentArea =
1293 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131294 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171295 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131296 self.typingShield.autoresizingMask = initialViewAutoresizing;
1297 [self.typingShield addTarget:self
1298 action:@selector(shieldWasTapped:)
1299 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131300 self.view.autoresizingMask = initialViewAutoresizing;
1301 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1302 [self.view addSubview:self.contentArea];
1303 [self.view addSubview:self.typingShield];
1304 [super viewDidLoad];
1305
1306 // Install fake status bar for iPad iOS7
1307 [self installFakeStatusBar];
1308 [self buildToolbarAndTabStrip];
1309 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501310 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
1311 [self addConstraintsToToolbar];
1312 }
sdefresnee65fd872016-12-19 13:38:131313 // If the tab model and browser state are valid, finish initialization.
1314 if (_model && _browserState)
1315 [self addUIFunctionalityForModelAndBrowserState];
1316
1317 // Add a tap gesture recognizer to save the last tap location for the source
1318 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271319 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1320 initWithTarget:self
1321 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131322 [tapRecognizer setDelegate:self];
1323 [tapRecognizer setCancelsTouchesInView:NO];
1324 [_contentArea addGestureRecognizer:tapRecognizer];
1325}
1326
Justin Cohenb3170c32017-09-19 01:55:221327- (void)viewSafeAreaInsetsDidChange {
1328 [super viewSafeAreaInsetsDidChange];
1329 // Gate this behind iPhone X, since it's currently the only device that
1330 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501331 if (IsIPhoneX()) {
Justin Cohenb3170c32017-09-19 01:55:221332 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501333 }
1334 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
1335 [_toolbarCoordinator.webToolbarController safeAreaInsetsDidChange];
Jean-François Geyelinbead9982017-10-25 11:52:071336 _toolbarCoordinator.webToolbarController.heightConstraint.constant =
Jean-François Geyelined4cde72017-10-11 11:34:501337 [_toolbarCoordinator.webToolbarController
1338 preferredToolbarHeightWhenAlignedToTopOfScreen];
1339 }
Justin Cohenb3170c32017-09-19 01:55:221340}
1341
sdefresnee65fd872016-12-19 13:38:131342- (void)viewDidAppear:(BOOL)animated {
1343 [super viewDidAppear:animated];
1344 self.viewVisible = YES;
1345 [self updateDialogPresenterActiveState];
Gregory Chatzinoff541b8642017-10-25 00:25:211346
1347 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1348 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1349 // check for |self.browserState| before calling the presenting the bubbles.
1350 if (self.browserState) {
1351 [self presentBubblesIfEligible];
1352 }
sdefresnee65fd872016-12-19 13:38:131353}
1354
1355- (void)viewWillAppear:(BOOL)animated {
1356 [super viewWillAppear:animated];
1357
1358 // Reparent the toolbar if it's been relinquished.
1359 if (_isToolbarControllerRelinquished)
1360 [self reparentToolbarController];
1361
1362 self.visible = YES;
1363
1364 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251365 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131366 _infoBarContainer->RestoreInfobars();
1367 }
1368
1369 // If the controller is suspended, or has been paged out due to low memory,
1370 // updating the view will be handled when it's displayed again.
1371 if (![_model webUsageEnabled] || !self.contentArea)
1372 return;
1373 // Update the displayed tab (if any; the switcher may not have created one
1374 // yet) in case it changed while showing the switcher.
1375 Tab* currentTab = [_model currentTab];
1376 if (currentTab)
1377 [self displayTab:currentTab isNewSelection:YES];
1378}
1379
1380- (void)viewWillDisappear:(BOOL)animated {
1381 self.viewVisible = NO;
1382 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131383 [[_model currentTab] wasHidden];
1384 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251385 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131386 _infoBarContainer->SuspendInfobars();
1387 }
1388 [super viewWillDisappear:animated];
1389}
1390
1391- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1392 duration:(NSTimeInterval)duration {
1393 [super willRotateToInterfaceOrientation:orient duration:duration];
1394 [self dismissPopups];
1395 [self reshowFindBarIfNeededWithCoordinator:nil];
1396}
1397
1398- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1399 [super didRotateFromInterfaceOrientation:orient];
1400
1401 // This reinitializes the toolbar, including updating the Overlay View,
1402 // if there is one.
1403 [self updateToolbar];
1404 [self infoBarContainerStateChanged:false];
1405}
1406
1407- (BOOL)prefersStatusBarHidden {
1408 return self.hideStatusBar;
1409}
1410
1411// Called when in the foreground and the OS needs more memory. Release as much
1412// as possible.
1413- (void)didReceiveMemoryWarning {
1414 // Releases the view if it doesn't have a superview.
1415 [super didReceiveMemoryWarning];
1416
1417 // Release any cached data, images, etc that aren't in use.
1418 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1419 // but since the FaviconCache uses obj-c in the header, it can't be included
1420 // there.
1421 if (_browserState) {
1422 FaviconLoader* loader =
1423 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1424 _browserState);
1425 if (loader)
1426 loader->PurgeCache();
1427 }
1428
1429 if (![self isViewLoaded]) {
1430 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1431 // as the BrowserViewController.
1432 self.contentArea = nil;
1433 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271434 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131435 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271436 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421437 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461438 _toolbarCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271439 _toolbarModelDelegate = nil;
1440 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031441 [self.tabStripCoordinator stop];
1442 self.tabStripCoordinator = nil;
1443 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271444 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131445 }
1446}
1447
1448- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1449 [super traitCollectionDidChange:previousTraitCollection];
1450 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1451 // because in some cases the presented view controller isn't a child of the
1452 // BVC in the view controller hierarchy (some intervening object isn't a
1453 // view controller).
1454 [self.presentedViewController
1455 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131456 // Update voice search bar visibility.
1457 [self updateVoiceSearchBarVisibilityAnimated:NO];
1458}
1459
1460- (void)viewWillTransitionToSize:(CGSize)size
1461 withTransitionCoordinator:
1462 (id<UIViewControllerTransitionCoordinator>)coordinator {
1463 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1464 [self dismissPopups];
1465 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1466}
1467
1468- (void)reshowFindBarIfNeededWithCoordinator:
1469 (id<UIViewControllerTransitionCoordinator>)coordinator {
1470 if (![_findBarController isFindInPageShown])
1471 return;
1472
1473 // Record focused state.
1474 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1475
1476 [self hideFindBarWithAnimation:NO];
1477
stkhapuginc9eee7b2017-04-10 15:49:271478 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131479 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1480 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271481 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131482 if (strongSelf)
1483 [strongSelf showFindBarWithAnimation:NO
1484 selectText:NO
1485 shouldFocus:isFocusedBeforeReshow];
1486 };
1487
1488 BOOL enqueued =
1489 [coordinator animateAlongsideTransition:nil completion:completion];
1490 if (!enqueued) {
1491 completion(nil);
1492 }
1493}
1494
1495- (void)dismissViewControllerAnimated:(BOOL)flag
1496 completion:(void (^)())completion {
1497 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271498 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131499 [super dismissViewControllerAnimated:flag
1500 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271501 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131502 [strongSelf setDismissingModal:NO];
1503 [strongSelf setPresenting:NO];
1504 if (completion)
1505 completion();
1506 [[strongSelf dialogPresenter] tryToPresent];
1507 }];
1508}
1509
1510- (void)presentViewController:(UIViewController*)viewControllerToPresent
1511 animated:(BOOL)flag
1512 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271513 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131514 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1515 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1516 // presented, to show a temporary view of the launch screen and then remove it
1517 // when the controller for the FRE has been presented. This fix should be
1518 // removed when the FRE startup code is rewritten.
1519 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1520 experimental_flags::AlwaysDisplayFirstRun()) &&
1521 !tests_hook::DisableFirstRun();
1522 // These if statements check that |presentViewController| is being called for
1523 // the FRE case.
1524 if (firstRunLaunch &&
1525 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1526 UINavigationController* navController =
1527 base::mac::ObjCCastStrict<UINavigationController>(
1528 viewControllerToPresent);
1529 if ([navController.topViewController
1530 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1531 self.hideStatusBar = YES;
1532
1533 // Load view from Launch Screen and add it to window.
1534 NSBundle* mainBundle = base::mac::FrameworkBundle();
1535 NSArray* topObjects =
1536 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1537 UIViewController* launchScreenController =
1538 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1539 // |launchScreenView| is loaded as an autoreleased object, and is retained
1540 // by the |completion| block below.
1541 UIView* launchScreenView = launchScreenController.view;
1542 launchScreenView.userInteractionEnabled = NO;
1543 launchScreenView.frame = self.view.window.bounds;
1544 [self.view.window addSubview:launchScreenView];
1545
1546 // Replace the completion handler sent to the superclass with one which
1547 // removes |launchScreenView| and resets the status bar. If |completion|
1548 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271549 __weak BrowserViewController* weakSelf = self;
1550 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131551 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271552 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131553 if (completion)
1554 completion();
stkhapuginc9eee7b2017-04-10 15:49:271555 };
sdefresnee65fd872016-12-19 13:38:131556 }
1557 }
1558
1559 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171560 if ([_sideSwipeController inSwipe]) {
1561 [_sideSwipeController resetContentView];
1562 }
sdefresnee65fd872016-12-19 13:38:131563
1564 [super presentViewController:viewControllerToPresent
1565 animated:flag
1566 completion:finalCompletionHandler];
1567}
1568
1569#pragma mark - Notification handling
1570
1571- (void)registerForNotifications {
1572 DCHECK(_model);
1573 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1574 [defaultCenter addObserver:self
1575 selector:@selector(pageLoadStarting:)
1576 name:kTabModelTabWillStartLoadingNotification
1577 object:_model];
1578 [defaultCenter addObserver:self
1579 selector:@selector(pageLoadStarted:)
1580 name:kTabModelTabDidStartLoadingNotification
1581 object:_model];
1582 [defaultCenter addObserver:self
1583 selector:@selector(pageLoadComplete:)
1584 name:kTabModelTabDidFinishLoadingNotification
1585 object:_model];
1586 [defaultCenter addObserver:self
1587 selector:@selector(tabDeselected:)
1588 name:kTabModelTabDeselectedNotification
1589 object:_model];
1590 [defaultCenter addObserver:self
1591 selector:@selector(tabWasAdded:)
1592 name:kTabModelNewTabWillOpenNotification
1593 object:_model];
1594}
1595
1596- (void)pageLoadStarting:(NSNotification*)notify {
1597 Tab* tab = notify.userInfo[kTabModelTabKey];
1598 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511599
1600 // Stop any Find in Page searches and close the find bar when navigating to a
1601 // new page.
1602 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361603
sdefresnee65fd872016-12-19 13:38:131604 if (tab == [_model currentTab]) {
1605 // TODO(pinkerton): Fill in here about hiding the forward button on
1606 // navigation.
1607 }
1608}
1609
1610- (void)pageLoadStarted:(NSNotification*)notify {
1611 Tab* tab = notify.userInfo[kTabModelTabKey];
1612 DCHECK(tab);
1613 if (tab == [_model currentTab]) {
1614 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461615 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131616 }
1617 [self updateVoiceSearchBarVisibilityAnimated:NO];
1618 }
1619}
1620
1621- (void)pageLoadComplete:(NSNotification*)notify {
1622 // Update the UI, but only if the current tab.
1623 Tab* tab = notify.userInfo[kTabModelTabKey];
1624 if (tab == [_model currentTab]) {
1625 // There isn't any need to update the toolbar here. When the page finishes,
1626 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1627 }
1628
1629 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1630
1631 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1632}
1633
1634- (void)tabDeselected:(NSNotification*)notify {
1635 DCHECK(notify);
1636 Tab* tab = notify.userInfo[kTabModelTabKey];
1637 DCHECK(tab);
1638 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221639 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131640}
1641
1642- (void)tabWasAdded:(NSNotification*)notify {
1643 Tab* tab = notify.userInfo[kTabModelTabKey];
1644 DCHECK(tab);
1645
Eugene But56efc322017-08-11 14:03:441646 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131647
1648 // When adding new tabs, check what kind of reminder infobar should
1649 // be added to the new tab. Try to add only one of them.
1650 // This check is done when a new tab is added either through the Tools Menu
1651 // "New Tab" or through "New Tab" in Stack View Controller. This method
1652 // is called after a new tab has added and finished initial navigation.
1653 // If this is added earlier, the initial navigation may end up clearing
1654 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301655 web::WebState* webState = tab.webState;
1656 DCHECK(webState);
1657
1658 infobars::InfoBarManager* infoBarManager =
1659 InfoBarManagerImpl::FromWebState(webState);
1660 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131661 forTabId:[tab tabId]];
edchinbb8ba892017-09-12 15:44:031662 if (!ReSignInInfoBarDelegate::Create(_browserState, tab, self.dispatcher)) {
edchin9cad67b2017-09-11 20:13:571663 DisplaySyncErrors(_browserState, tab, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:131664 }
1665
1666 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501667 // phone-specific. Call the foreground tab added completion block; for
1668 // iPhones, this will get executed after the animation has finished.
1669 if (IsIPadIdiom()) {
1670 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431671 // This callback is called before webState is activated (on
1672 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1673 // callback asynchronously to be sure the activation is complete.
1674 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011675 // Test existence again as the block may have been deleted.
1676 if (self.foregroundTabWasAddedCompletionBlock) {
1677 self.foregroundTabWasAddedCompletionBlock();
1678 self.foregroundTabWasAddedCompletionBlock = nil;
1679 }
Olivier Robinc7e46242017-09-06 07:55:431680 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501681 }
sdefresnee65fd872016-12-19 13:38:131682 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501683 }
sdefresnee65fd872016-12-19 13:38:131684
1685 // Do nothing if browsing is currently suspended. The BVC will set everything
1686 // up correctly when browsing resumes.
1687 if (!self.visible || ![_model webUsageEnabled])
1688 return;
1689
1690 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1691
1692 // Block that starts voice search at the end of new Tab animation if
1693 // necessary.
1694 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1695 if (_startVoiceSearchAfterNewTabAnimation) {
1696 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001697 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131698 }
1699 };
1700
kkhorimotoa44349c12017-04-12 23:02:121701 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131702 if (!inBackground) {
1703 UIView* animationParentView = _contentArea;
1704 // Create the new page image, and load with the new tab page snapshot.
1705 CGFloat newPageOffset = 0;
1706 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191707 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1708 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131709 animationParentView = self.view;
1710 newPage = [self pageFullScreenOpenCloseAnimationView];
1711 } else {
1712 newPage = [self pageOpenCloseAnimationView];
1713 }
1714 newPageOffset = newPage.frame.origin.y;
1715
1716 [tab view].frame = _contentArea.bounds;
1717 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1718 [animationParentView addSubview:newPage];
1719 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521720 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131721 newPage, -newPageOffset,
1722 newPage.frame.size.height - newPage.image.size.height, origin,
1723 _isOffTheRecord, NULL, ^{
1724 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121725 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421726 // Use the model's currentTab here because it is possible that it can
1727 // be reset to a new value before the new Tab animation finished (e.g.
1728 // if another Tab shows a dialog via |dialogPresenter|). However, that
1729 // tab's view hasn't been displayed yet because it was in a new tab
1730 // animation.
1731 Tab* currentTab = [_model currentTab];
1732 if (currentTab) {
1733 [self tabSelected:currentTab];
1734 }
sdefresnee65fd872016-12-19 13:38:131735 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391736
1737 if (self.foregroundTabWasAddedCompletionBlock) {
1738 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451739 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391740 }
sdefresnee65fd872016-12-19 13:38:131741 });
1742 } else {
1743 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1744 // snapshot before adding the views needed for the background animation.
1745 Tab* topTab = [_model currentTab];
1746 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1747 visibleFrameOnly:self.isToolbarOnScreen];
1748 // Add three layers in order on top of the contentArea for the animation:
1749 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271750 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131751 InstallBackgroundInView(background);
1752 [_contentArea addSubview:background];
1753
1754 // 2. A CardView displaying the data from the current tab.
1755 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1756 NSString* title = [topTab title];
1757 if (![title length])
1758 title = [topTab urlDisplayString];
1759 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131760 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371761 [topCard setFavicon:nil];
1762
1763 favicon::FaviconDriver* faviconDriver =
1764 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1765 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1766 gfx::Image favicon = faviconDriver->GetFavicon();
1767 if (!favicon.IsEmpty())
1768 [topCard setFavicon:favicon.ToUIImage()];
1769 }
sdefresnee65fd872016-12-19 13:38:131770
1771 // 3. A new, blank CardView to represent the new tab being added.
1772 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521773 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131774 topCard, [_contentArea frame], IsPortrait(), ^{
1775 [background removeFromSuperview];
1776 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121777 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131778 // Resnapshot the top card if it has its own toolbar, as the toolbar
1779 // will be captured in the new tab animation, but isn't desired for
1780 // the stack view snapshots.
1781 id nativeController = [self nativeControllerForTab:topTab];
1782 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1783 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1784 startVoiceSearchIfNecessaryBlock();
1785 });
peterlaurens9f1b6e02017-06-22 17:46:451786 // Reset the foreground tab completion block so that it can never be
1787 // called more than once regardless of foreground/background tab
1788 // appearances.
1789 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131790 }
1791}
1792
1793#pragma mark - UI Configuration and Layout
1794
1795- (void)updateWithTabModel:(TabModel*)model
1796 browserState:(ios::ChromeBrowserState*)browserState {
1797 DCHECK(model);
1798 DCHECK(browserState);
1799 DCHECK(!_model);
1800 DCHECK(!_browserState);
1801 _browserState = browserState;
1802 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271803 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381804
sdefresnee65fd872016-12-19 13:38:131805 [_model addObserver:self];
1806
1807 if (!_isOffTheRecord) {
1808 [DefaultIOSWebViewFactory
1809 registerWebViewFactory:[ChromeWebViewFactory class]];
1810 }
1811 NSUInteger count = [_model count];
1812 for (NSUInteger index = 0; index < count; ++index)
1813 [self installDelegatesForTab:[_model tabAtIndex:index]];
1814
1815 [self registerForNotifications];
1816
gambardbdc07cc2017-02-03 16:43:111817 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121818 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271819 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131820
sdefresnedc432f42017-01-17 14:36:591821 // Register for bookmark changed notification (BookmarkModel may be null
1822 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131823 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591824 if (_bookmarkModel) {
1825 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1826 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1827 }
sdefresnee65fd872016-12-19 13:38:131828}
1829
sdefresnee65fd872016-12-19 13:38:131830- (void)browserStateDestroyed {
1831 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131832 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271833 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461834 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131835 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381836
1837 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431838 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111839 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381840 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271841 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351842 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031843 [self.tabStripCoordinator stop];
1844 self.tabStripCoordinator = nil;
1845 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381846
sdefresnee65fd872016-12-19 13:38:131847 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391848 [_dispatcher stopDispatchingToTarget:self];
1849 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131850}
1851
1852- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221853 CGFloat statusBarHeight = StatusBarHeight();
1854 CGRect statusBarFrame =
1855 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1856 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1857 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131858 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221859 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1860 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1861 [_fakeStatusBarView layer].zPosition = 99;
1862 [[self view] addSubview:_fakeStatusBarView];
1863 } else {
1864 // Add a white bar on phone so that the status bar on the NTP is white.
1865 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1866 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131867 }
1868}
1869
1870// Create the UI elements. May or may not have valid browser state & tab model.
1871- (void)buildToolbarAndTabStrip {
1872 DCHECK([self isViewLoaded]);
1873 DCHECK(!_toolbarModelDelegate);
1874
Rohit Rao44f204302017-08-10 14:49:541875 // Initialize the prerender service before creating the toolbar controller.
1876 PrerenderService* prerenderService =
1877 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1878 if (prerenderService) {
1879 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131880 }
1881
1882 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201883 _toolbarModelDelegate.reset(
1884 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131885 _toolbarModelIOS.reset([_dependencyFactory
1886 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
sczsf1620e52017-10-02 22:54:461887 _toolbarCoordinator =
1888 [[LegacyToolbarCoordinator alloc] initWithBaseViewController:self];
1889 _toolbarCoordinator.tabModel = _model;
1890 _toolbarCoordinator.webToolbarController =
Mark Cogan6ebbde02017-07-07 12:50:131891 [_dependencyFactory newWebToolbarControllerWithDelegate:self
1892 urlLoader:self
Mark Cogan6ebbde02017-07-07 12:50:131893 dispatcher:self.dispatcher];
sczsf1620e52017-10-02 22:54:461894 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391895 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461896 [_toolbarCoordinator setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271897 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461898 _voiceSearchController->SetDelegate(
1899 _toolbarCoordinator.webToolbarController);
sdefresnee65fd872016-12-19 13:38:131900
sdefresnee65fd872016-12-19 13:38:131901 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031902 self.tabStripCoordinator =
1903 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1904 self.tabStripCoordinator.browserState = _browserState;
1905 self.tabStripCoordinator.dispatcher = _dispatcher;
1906 self.tabStripCoordinator.tabModel = _model;
1907 self.tabStripCoordinator.presentationProvider = self;
1908 self.tabStripCoordinator.animationWaitDuration =
1909 kFullScreenControllerToolbarAnimationDuration;
1910 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131911 }
1912
1913 // Create infobar container.
1914 if (!_infoBarContainerDelegate) {
1915 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1916 _infoBarContainer.reset(
1917 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1918 }
1919}
1920
Jean-François Geyelined4cde72017-10-11 11:34:501921- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111922 NSLayoutYAxisAnchor* topAnchor;
1923 // On iPad, the toolbar is underneath the tab strip.
1924 // On iPhone, it is underneath the top of the screen.
1925 if (IsIPadIdiom()) {
1926 topAnchor = self.tabStripView.bottomAnchor;
1927 } else {
1928 topAnchor = [self view].topAnchor;
1929 }
1930
Jean-François Geyelinbead9982017-10-25 11:52:071931 [_toolbarCoordinator.webToolbarController heightConstraint].constant =
1932 [_toolbarCoordinator.webToolbarController
1933 preferredToolbarHeightWhenAlignedToTopOfScreen];
1934 [_toolbarCoordinator.webToolbarController heightConstraint].active = YES;
Jean-François Geyelined4cde72017-10-11 11:34:501935
1936 [NSLayoutConstraint activateConstraints:@[
1937 [[_toolbarCoordinator view].leadingAnchor
1938 constraintEqualToAnchor:[self view].leadingAnchor],
Jean-François Geyelince0a4742017-10-25 12:34:111939 [[_toolbarCoordinator view].topAnchor constraintEqualToAnchor:topAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501940 [[_toolbarCoordinator view].trailingAnchor
1941 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501942 ]];
1943 [[self view] layoutIfNeeded];
1944}
1945
sdefresnee65fd872016-12-19 13:38:131946// Enable functionality that only makes sense if the views are loaded and
1947// both browser state and tab model are valid.
1948- (void)addUIFunctionalityForModelAndBrowserState {
1949 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051950 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131951 DCHECK(_model);
1952 DCHECK([self isViewLoaded]);
1953
1954 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1955
Rohit Raoaf46af92017-08-10 12:52:301956 infobars::InfoBarManager* infoBarManager = nullptr;
1957 if (_model.currentTab) {
1958 DCHECK(_model.currentTab.webState);
1959 infoBarManager =
1960 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1961 }
sdefresnee65fd872016-12-19 13:38:131962 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1963
sczsdd860eba2017-08-10 01:55:381964 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431965 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1966 initWithBaseViewController:self];
1967 _activityServiceCoordinator.dispatcher = _dispatcher;
1968 _activityServiceCoordinator.tabModel = _model;
1969 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461970 _activityServiceCoordinator.positionProvider =
1971 _toolbarCoordinator.webToolbarController;
Rohit Rao01e0e002017-08-14 20:49:431972 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:431973
Rohit Raocda0a992017-08-16 15:37:111974 _qrScannerCoordinator =
1975 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1976 _qrScannerCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:461977 _qrScannerCoordinator.loadProvider = _toolbarCoordinator.webToolbarController;
Rohit Raocda0a992017-08-16 15:37:111978 _qrScannerCoordinator.presentationProvider = self;
1979
sczsdd860eba2017-08-10 01:55:381980 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131981 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381982 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:461983 _tabHistoryCoordinator.positionProvider =
1984 _toolbarCoordinator.webToolbarController;
sczsdd860eba2017-08-10 01:55:381985 _tabHistoryCoordinator.tabModel = _model;
1986 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:461987 _tabHistoryCoordinator.tabHistoryUIUpdater =
1988 _toolbarCoordinator.webToolbarController;
sczsdd860eba2017-08-10 01:55:381989
sczs6ae47ad2017-09-06 17:26:531990 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
1991 _sadTabCoordinator.dispatcher = _dispatcher;
1992
Gregory Chatzinoffdf93d692017-09-09 01:32:271993 _pageInfoCoordinator =
1994 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
1995 _pageInfoCoordinator.browserState = _browserState;
1996 _pageInfoCoordinator.dispatcher = _dispatcher;
1997 _pageInfoCoordinator.loader = self;
1998 _pageInfoCoordinator.presentationProvider = self;
1999 _pageInfoCoordinator.tabModel = _model;
2000
Louis Romerod11747a2017-10-20 20:10:352001 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2002 _externalSearchCoordinator.dispatcher = _dispatcher;
2003
mathp9b4c11d2017-07-06 20:24:132004 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272005 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132006 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202007 browserState:_browserState
2008 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052009 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192010 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132011 }
2012}
2013
2014// Set the frame for the various views. View must be loaded.
2015- (void)setUpViewLayout {
2016 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132017 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132018 CGFloat minY = [self headerOffset];
2019
Justin Cohenb3170c32017-09-19 01:55:222020 // Update the fake toolbar background height.
2021 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2022 fakeStatusBarFrame.size.height = StatusBarHeight();
2023 _fakeStatusBarView.frame = fakeStatusBarFrame;
2024
edchinf5150c682017-09-18 02:50:032025 if (self.tabStripView) {
2026 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132027 }
2028
2029 // Position the toolbar next, either at the top of the browser view or
2030 // directly under the tabstrip.
sczsf1620e52017-10-02 22:54:462031 CGRect toolbarFrame = [[_toolbarCoordinator view] frame];
sdefresnee65fd872016-12-19 13:38:132032 toolbarFrame.origin = CGPointMake(0, minY);
2033 toolbarFrame.size.width = widthOfView;
Jean-François Geyelined4cde72017-10-11 11:34:502034 if (!base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
2035 [[_toolbarCoordinator view] setFrame:toolbarFrame];
2036 }
sdefresnee65fd872016-12-19 13:38:132037
2038 // Place the infobar container above the content area.
2039 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
2040 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
2041
2042 // Place the toolbar controller above the infobar container.
sczsf1620e52017-10-02 22:54:462043 [[self view] insertSubview:[_toolbarCoordinator view]
sdefresnee65fd872016-12-19 13:38:132044 aboveSubview:infoBarContainerView];
2045 minY += CGRectGetHeight(toolbarFrame);
2046
2047 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2048 // content slightly.
sczs8c837782017-10-03 02:57:242049 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132050
2051 // Adjust the content area to be under the toolbar, for fullscreen or below
2052 // the toolbar is not fullscreen.
2053 CGRect contentFrame = [_contentArea frame];
2054 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222055 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2056 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132057 [_contentArea setFrame:contentFrame];
2058
2059 // Adjust the infobar container to be either at the bottom of the screen
2060 // (iPhone) or on the lower toolbar edge (iPad).
2061 CGRect infoBarFrame = contentFrame;
2062 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2063 infoBarFrame.size.height = 0;
2064 [infoBarContainerView setFrame:infoBarFrame];
2065
2066 // Attach the typing shield to the content area but have it hidden.
2067 [_typingShield setFrame:[_contentArea frame]];
2068 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
2069 [_typingShield setHidden:YES];
2070 _typingShield.accessibilityIdentifier = @"Typing Shield";
2071 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
2072}
2073
sdefresnee65fd872016-12-19 13:38:132074- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2075 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442076 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132077
kkhorimotoa44349c12017-04-12 23:02:122078 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132079 // Hide findbar. |updateToolbar| will restore the findbar later.
2080 [self hideFindBarWithAnimation:NO];
2081
2082 // Make new content visible, resizing it first as the orientation may
2083 // have changed from the last time it was displayed.
2084 [[tab view] setFrame:_contentArea.bounds];
2085 [_contentArea displayContentView:[tab view]];
2086 }
2087 [self updateToolbar];
2088
2089 if (newSelection)
sczsf1620e52017-10-02 22:54:462090 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132091
2092 // Notify the Tab that it was displayed.
2093 [tab wasShown];
2094}
2095
2096- (void)initializeBookmarkInteractionController {
2097 if (_bookmarkInteractionController)
2098 return;
edchinbb8ba892017-09-12 15:44:032099 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2100 initWithBrowserState:_browserState
2101 loader:self
2102 parentController:self
2103 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132104}
2105
2106// Update the state of back and forward buttons, hiding the forward button if
2107// there is nowhere to go. Assumes the model's current tab is up to date.
2108- (void)updateToolbar {
2109 // If the BVC has been partially torn down for low memory, wait for the
2110 // view rebuild to handle toolbar updates.
2111 if (!(_toolbarModelIOS && _browserState))
2112 return;
2113
2114 Tab* tab = [_model currentTab];
2115 if (![tab navigationManager])
2116 return;
sczsf1620e52017-10-02 22:54:462117 [_toolbarCoordinator updateToolbarState];
2118 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132119
Rohit Rao44f204302017-08-10 14:49:542120 PrerenderService* prerenderService =
2121 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2122 BOOL isPrerenderTab =
2123 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2124 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462125 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132126
2127 // Also update the loading state for the tools menu (that is really an
2128 // extension of the toolbar on the iPhone).
2129 if (!IsIPadIdiom())
sczsf1620e52017-10-02 22:54:462130 [[_toolbarCoordinator toolsPopupController]
sdefresnee65fd872016-12-19 13:38:132131 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2132
rohitrao005a6432017-03-16 20:52:422133 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2134 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132135 [self showFindBarWithAnimation:NO
2136 selectText:YES
2137 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362138 }
sdefresnee65fd872016-12-19 13:38:132139
2140 // Hide the toolbar if displaying phone NTP.
2141 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552142 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132143 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552144 if (item) {
2145 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132146 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2147 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462148 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2149 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132150 }
sczsf1620e52017-10-02 22:54:462151 [[_toolbarCoordinator view] setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132152 }
2153}
2154
2155- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122156 self.dialogPresenter.active =
2157 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132158}
2159
2160- (void)dismissPopups {
sczsf1620e52017-10-02 22:54:462161 [_toolbarCoordinator dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272162 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282163 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472164 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382165}
2166
Cooper Knaakd0a974cd2017-08-10 18:05:472167- (BubbleViewControllerPresenter*)
2168bubblePresenterForFeature:(const base::Feature&)feature
2169 direction:(BubbleArrowDirection)direction
2170 alignment:(BubbleAlignment)alignment
2171 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212172 DCHECK(self.browserState);
2173 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382174 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472175 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382176 }
2177 // Capture |weakSelf| instead of the feature engagement tracker object
2178 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2179 // the feature engagement tracker will remain pointing to invalid memory if
2180 // its owner (the ChromeBrowserState) is deallocated.
2181 __weak BrowserViewController* weakSelf = self;
2182 void (^dismissalCallback)(void) = ^() {
2183 BrowserViewController* strongSelf = weakSelf;
2184 if (strongSelf) {
2185 feature_engagement::TrackerFactory::GetForBrowserState(
2186 strongSelf.browserState)
2187 ->Dismissed(feature);
2188 }
2189 };
2190
Cooper Knaakd0a974cd2017-08-10 18:05:472191 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382192 [[BubbleViewControllerPresenter alloc] initWithText:text
2193 arrowDirection:direction
2194 alignment:alignment
2195 dismissalCallback:dismissalCallback];
2196
Cooper Knaakd0a974cd2017-08-10 18:05:472197 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132198}
2199
Cooper Knaak120cee5e2017-08-10 20:57:002200- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212201 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002202 // If the tab tip bubble has already been presented and the user is still
2203 // considered engaged, it can't be overwritten or set to |nil| or else it will
2204 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2205 // can be safely overwritten or set to |nil|.
2206 if (!self.tabTipBubblePresenter.isUserEngaged) {
2207 __weak BrowserViewController* weakSelf = self;
2208 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2209 [weakSelf presentNewTabTipBubble];
2210 };
2211
2212 // Because the new tab tip occurs on startup, the feature engagement
2213 // tracker's database is not guaranteed to be loaded by this time. For the
2214 // bubble to appear properly, a callback is used to guarantee the event data
2215 // is loaded before the check to see if the promotion should be displayed.
2216 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2217 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2218 }
2219}
2220
2221- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212222 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002223 NSString* text =
2224 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2225 CGPoint tabSwitcherAnchor;
2226 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032227 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002228 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032229 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002230 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2231 } else {
sczsf1620e52017-10-02 22:54:462232 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002233 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462234 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002235 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2236 }
Cooper Knaake963d6702017-08-11 21:03:112237 // If the feature engagement tracker does not consider it valid to display
2238 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2239 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002240 self.tabTipBubblePresenter =
2241 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2242 direction:BubbleArrowDirectionUp
2243 alignment:BubbleAlignmentTrailing
2244 text:text];
2245 [self.tabTipBubblePresenter presentInViewController:self
2246 view:self.view
2247 anchorPoint:tabSwitcherAnchor];
2248}
2249
Helen Yang9175bd52017-08-12 00:28:402250- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212251 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402252 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2253 // user is still considered engaged.
2254 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2255 __weak BrowserViewController* weakSelf = self;
2256 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2257 [weakSelf presentNewIncognitoTabTipBubble];
2258 };
2259
2260 // Use a callback in case the new incognito tab tip should be shown on
2261 // startup. This ensures that the tracker's database will be fully loaded
2262 // before checking if the promotion should be displayed.
2263 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2264 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2265 }
2266}
2267
2268- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212269 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462270 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402271 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2272 NSString* text = l10n_util::GetNSStringWithFixup(
2273 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462274 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402275 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2276 self.incognitoTabTipBubblePresenter =
2277 [self bubblePresenterForFeature:feature_engagement::
2278 kIPHNewIncognitoTabTipFeature
2279 direction:BubbleArrowDirectionUp
2280 alignment:BubbleAlignmentTrailing
2281 text:text];
2282 [self.incognitoTabTipBubblePresenter
2283 presentInViewController:self
2284 view:self.view
2285 anchorPoint:toolsButtonAnchor];
2286 // Only trigger the tools menu button animation if the bubble is shown.
2287 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462288 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402289 }
2290}
2291
sdefresnee65fd872016-12-19 13:38:132292#pragma mark - Tap handling
2293
Mark Cogandfcdea72017-07-18 13:47:382294- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032295 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2296 _lastTapPoint = CGPointZero;
2297 } else {
2298 _lastTapPoint =
2299 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132300 }
Mark Cogane01ebce2017-07-12 19:31:032301 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132302}
2303
2304- (CGPoint)lastTapPoint {
2305 if (CACurrentMediaTime() - _lastTapTime < 1) {
2306 return _lastTapPoint;
2307 }
2308 return CGPointZero;
2309}
2310
2311- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2312 UIView* view = gestureRecognizer.view;
2313 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2314 _lastTapPoint =
2315 [[view superview] convertPoint:viewCoordinate toView:self.view];
2316 _lastTapTime = CACurrentMediaTime();
2317}
2318
2319- (BOOL)addTabIfNoTabWithNormalBrowserState {
2320 if (![_model count]) {
2321 if (!_isOffTheRecord) {
2322 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2323 transition:ui::PAGE_TRANSITION_TYPED];
2324 return YES;
2325 }
2326 }
2327 return NO;
2328}
2329
2330#pragma mark - Tab creation and selection
2331
2332// Called when either a tab finishes loading or when a tab with finished content
2333// is added directly to the model via pre-rendering.
2334- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2335 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2336
2337 // Persist the session on a delay.
2338 [_model saveSessionImmediately:NO];
2339}
2340
2341- (Tab*)addSelectedTabWithURL:(const GURL&)url
2342 postData:(TemplateURLRef::PostContent*)postData
2343 transition:(ui::PageTransition)transition {
2344 return [self addSelectedTabWithURL:url
2345 postData:postData
2346 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492347 transition:transition
2348 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132349}
2350
2351- (Tab*)addSelectedTabWithURL:(const GURL&)url
2352 transition:(ui::PageTransition)transition {
2353 return [self addSelectedTabWithURL:url
2354 atIndex:[_model count]
2355 transition:transition];
2356}
2357
2358- (Tab*)addSelectedTabWithURL:(const GURL&)url
2359 atIndex:(NSUInteger)position
2360 transition:(ui::PageTransition)transition {
2361 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492362 atIndex:position
2363 transition:transition
2364 tabAddedCompletion:nil];
2365}
2366
2367- (Tab*)addSelectedTabWithURL:(const GURL&)url
2368 atIndex:(NSUInteger)position
2369 transition:(ui::PageTransition)transition
2370 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2371 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132372 postData:NULL
2373 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492374 transition:transition
2375 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132376}
2377
2378- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2379 postData:(TemplateURLRef::PostContent*)postData
2380 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492381 transition:(ui::PageTransition)transition
2382 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132383 if (position == NSNotFound)
2384 position = [_model count];
2385 DCHECK(position <= [_model count]);
2386
2387 web::NavigationManager::WebLoadParams params(URL);
2388 params.transition_type = transition;
2389 if (postData) {
2390 // Extract the content type and post params from |postData| and add them
2391 // to the load params.
2392 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2393 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2394 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172395 params.post_data.reset(data);
2396 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132397 }
Olivier Robind508a5632017-07-19 16:29:492398
2399 if (tabAddedCompletion) {
2400 if (self.foregroundTabWasAddedCompletionBlock) {
2401 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2402 self.foregroundTabWasAddedCompletionBlock;
2403 self.foregroundTabWasAddedCompletionBlock = ^{
2404 oldForegroundTabWasAddedCompletionBlock();
2405 tabAddedCompletion();
2406 };
2407 } else {
2408 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2409 }
2410 }
2411
sdefresnea6395912017-03-01 01:14:352412 Tab* tab = [_model insertTabWithLoadParams:params
2413 opener:nil
2414 openedByDOM:NO
2415 atIndex:position
2416 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132417 return tab;
2418}
2419
olivierrobin889af53f2017-03-01 14:56:322420// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132421- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322422 web::WebState* webState = tab.webState;
2423 if (!webState)
2424 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392425 web::NavigationItem* visibleItem =
2426 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322427 if (!visibleItem)
2428 return NO;
2429 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132430}
2431
2432- (void)expectNewForegroundTab {
2433 _expectingForegroundTab = YES;
2434}
2435
2436- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2437 CGRect viewBounds, remainder;
2438 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2439 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172440 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132441}
2442
2443- (UIImageView*)pageOpenCloseAnimationView {
2444 CGRect frame = [_contentArea bounds];
2445
2446 frame.size.height = frame.size.height - [self headerHeight];
2447 frame.origin.y = [self headerHeight];
2448
stkhapuginf58b10d02017-04-10 13:36:172449 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132450 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2451 pageView.center = center;
2452
2453 pageView.backgroundColor = [UIColor whiteColor];
2454 return pageView;
2455}
2456
2457- (void)installDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112458 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
sdefresne49cf2862017-03-15 13:46:142459 // Unregistration happens when the Tab is removed from the TabModel.
Mike Doughertya1ec26402017-08-23 19:46:312460 tab.iOSCaptivePortalBlockingPageDelegate = self;
edchincd32fdf2017-10-25 12:45:452461
2462 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2463 if (PasswordTabHelper* passwordTabHelper =
2464 PasswordTabHelper::FromWebState(tab.webState)) {
2465 passwordTabHelper->SetDispatcher(self.dispatcher);
2466 passwordTabHelper->SetPasswordControllerDelegate(self);
2467 }
2468
sdefresnee65fd872016-12-19 13:38:132469 tab.dialogDelegate = self;
2470 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132471 tab.passKitDialogProvider = self;
2472 tab.fullScreenControllerDelegate = self;
2473 if (!IsIPadIdiom()) {
2474 tab.overscrollActionsControllerDelegate = self;
2475 }
olivierrobin9ce77b82017-01-12 17:29:192476 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132477 tab.tabSnapshottingDelegate = self;
2478 // Install the proper CRWWebController delegates.
2479 tab.webController.nativeProvider = self;
2480 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512481 // BrowserViewController presents SKStoreKitViewController on behalf of a
2482 // tab.
2483 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2484 if (tabHelper)
2485 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132486 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532487 // BrowserViewController owns the coordinator that displays the Sad Tab.
2488 if (!SadTabTabHelper::FromWebState(tab.webState))
2489 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
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;
2513 tab.fullScreenControllerDelegate = nil;
2514 if (!IsIPadIdiom()) {
2515 tab.overscrollActionsControllerDelegate = nil;
2516 }
2517 tab.tabHeadersDelegate = nil;
2518 tab.tabSnapshottingDelegate = nil;
2519 tab.webController.nativeProvider = nil;
2520 tab.webController.swipeRecognizerProvider = nil;
2521 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2522 if (tabHelper)
2523 tabHelper->SetLauncher(nil);
2524 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452525 if (AccountConsistencyService* accountConsistencyService =
2526 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2527 self.browserState)) {
2528 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2529 }
sdefresne49cf2862017-03-15 13:46:142530}
2531
sdefresnee65fd872016-12-19 13:38:132532// Called when a tab is selected in the model. Make any required view changes.
2533// The notification will not be sent when the tab is already the selected tab.
2534- (void)tabSelected:(Tab*)tab {
2535 DCHECK(tab);
2536
2537 // Ignore changes while the tab stack view is visible (or while suspended).
2538 // The display will be refreshed when this view becomes active again.
2539 if (!self.visible || ![_model webUsageEnabled])
2540 return;
2541
2542 [self displayTab:tab isNewSelection:YES];
2543
kkhorimotoa44349c12017-04-12 23:02:122544 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132545 // Now that the new tab has been displayed, return to normal. Rather than
2546 // keep a reference to the previous tab, just turn off preview mode for all
2547 // tabs (since doing so is a no-op for the tabs that don't have it set).
2548 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272549 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132550 [tab.webController setOverlayPreviewMode:NO];
2551 }
2552 }
2553}
2554
edchinf5150c682017-09-18 02:50:032555- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2556 return [self.tabStripCoordinator placeholderView];
2557}
2558
Sylvain Defresne41170aa2017-06-15 10:25:202559- (void)shutdown {
2560 DCHECK(!_isShutdown);
2561 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032562 [self.tabStripCoordinator stop];
2563 self.tabStripCoordinator = nil;
2564 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202565 _infoBarContainer = nil;
2566 _readingListMenuNotifier = nil;
2567 if (_bookmarkModel)
2568 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2569 [_model removeObserver:self];
2570 [[UpgradeCenter sharedInstance] unregisterClient:self];
2571 [[NSNotificationCenter defaultCenter] removeObserver:self];
sczsf1620e52017-10-02 22:54:462572 [_toolbarCoordinator.webToolbarController setDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202573 if (_voiceSearchController)
2574 _voiceSearchController->SetDelegate(nil);
2575 [_rateThisAppDialog setDelegate:nil];
2576 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462577 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202578}
2579
sdefresnee65fd872016-12-19 13:38:132580#pragma mark - SnapshotOverlayProvider methods
2581
2582- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2583 NSMutableArray* overlays = [NSMutableArray array];
2584 if (![_model webUsageEnabled]) {
2585 return overlays;
2586 }
2587 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2588 if (voiceSearchView) {
2589 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272590 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132591 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272592 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132593 [overlays addObject:voiceSearchOverlay];
2594 }
2595 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2596 if (infoBarView) {
2597 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272598 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132599 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272600 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132601 [overlays addObject:infoBarOverlay];
2602 }
2603 return overlays;
2604}
2605
2606#pragma mark -
2607
2608- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2609 if (IsIPadIdiom()) {
2610 // Not using overlays on iPad because the content is pushed down by
2611 // infobar and the transition between snapshot and fresh page can
2612 // cause both snapshot and real infobars to appear at the same time.
2613 return nil;
2614 }
2615 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302616 if (currentTab && tab == currentTab) {
2617 DCHECK(currentTab.webState);
2618 infobars::InfoBarManager* infoBarManager =
2619 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132620 if (infoBarManager->infobar_count() > 0) {
2621 DCHECK(_infoBarContainer);
2622 return _infoBarContainer->view();
2623 }
2624 }
2625 return nil;
2626}
2627
2628- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272629 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132630 // There is no UI representation for non-current tabs or there is
2631 // no _infoBarContainer instantiated yet.
2632 // Return offset outside of tab.
2633 return CGRectGetMaxY(self.view.frame);
2634 } else if (IsIPadIdiom()) {
2635 // The infobars on iPad are display at the top of a tab.
2636 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2637 } else {
2638 // The infobars on iPhone are displayed at the bottom of a tab.
2639 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2640 return CGRectGetMaxY(visibleFrame) -
2641 CGRectGetHeight(_infoBarContainer->view().frame);
2642 }
2643}
2644
2645- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2646 Tab* currentTab = [_model currentTab];
2647 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2648 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2649 return _voiceSearchBar;
2650 }
2651 return nil;
2652}
2653
2654- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2655 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2656 // There is no UI representation for non-current tabs or there is
2657 // no visible voice search. Return offset outside of tab.
2658 return CGRectGetMaxY(self.view.frame);
2659 } else {
2660 // The voice search bar on iPhone is displayed at the bottom of a tab.
2661 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2662 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2663 }
2664}
2665
2666- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272667 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132668 VoiceSearchProvider* provider =
2669 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2670 if (provider) {
2671 _voiceSearchController =
2672 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462673 _voiceSearchController->SetDelegate(
2674 _toolbarCoordinator.webToolbarController);
sdefresnee65fd872016-12-19 13:38:132675 }
2676 }
2677}
2678
2679- (void)ensureVoiceSearchBarCreated {
2680 if (_voiceSearchBar)
2681 return;
2682
2683 CGFloat width = CGRectGetWidth([[self view] bounds]);
2684 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2685 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272686 _voiceSearchBar = ios::GetChromeBrowserProvider()
2687 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002688 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132689 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2690 [_voiceSearchBar setHidden:YES];
2691 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2692 UIViewAutoresizingFlexibleWidth];
2693 [self.view insertSubview:_voiceSearchBar
2694 belowSubview:_infoBarContainer->view()];
2695}
2696
2697- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2698 // Voice search bar exists and is shown/hidden.
2699 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272700 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132701 return;
2702
2703 // Voice search bar doesn't exist and thus is not visible.
2704 if (!_voiceSearchBar && !show)
2705 return;
2706
2707 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272708 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132709 else
stkhapuginc9eee7b2017-04-10 15:49:272710 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132711}
2712
2713- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2714 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2715 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2716 self.shouldShowVoiceSearchBar) {
2717 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2718 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272719 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132720 }
2721 id currentNativeController =
2722 [self nativeControllerForTab:self.tabModel.currentTab];
2723 Protocol* possibleOwnerProtocol =
2724 @protocol(LogoAnimationControllerOwnerOwner);
2725 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2726 [currentNativeController logoAnimationControllerOwner]) {
2727 // If the current native controller is showing a GLIF view (e.g. the NTP
2728 // when there is no doodle), use that GLIFControllerOwner.
2729 return [currentNativeController logoAnimationControllerOwner];
2730 }
2731 return nil;
2732}
2733
2734#pragma mark - PassKitDialogProvider methods
2735
2736- (void)presentPassKitDialog:(NSData*)data {
2737 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272738 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132739 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272740 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132741 if (error || !data) {
2742 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302743 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132744 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302745 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132746 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2747 // NULL, replace if with DCHECK).
2748 if (infoBarManager)
2749 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2750 }
2751 } else {
2752 PKAddPassesViewController* passKitViewController =
2753 [_dependencyFactory newPassKitViewControllerForPass:pass];
2754 if (passKitViewController) {
2755 [self presentViewController:passKitViewController
2756 animated:YES
2757 completion:^{
2758 }];
2759 }
2760 }
2761}
2762
2763- (UIStatusBarStyle)preferredStatusBarStyle {
2764 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2765 : UIStatusBarStyleDefault;
2766}
2767
Tomasz Garbusb844e992017-09-29 12:44:552768#pragma mark - PasswordControllerDelegate methods
2769
2770- (BOOL)displaySignInNotification:(UIViewController*)viewController
2771 fromTabId:(NSString*)tabId {
2772 // Check if the call comes from currently visible tab.
2773 if ([tabId isEqual:[_model currentTab].tabId]) {
2774 [self addChildViewController:viewController];
2775 [self.view addSubview:viewController.view];
2776 [viewController didMoveToParentViewController:self];
2777 return YES;
2778 } else {
2779 return NO;
2780 }
2781}
2782
sdefresnee65fd872016-12-19 13:38:132783#pragma mark - CRWWebStateDelegate methods.
2784
eugenebut75a06fa72017-01-09 17:09:552785- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512786 createNewWebStateForURL:(const GURL&)URL
2787 openerURL:(const GURL&)openerURL
2788 initiatedByUser:(BOOL)initiatedByUser {
2789 // Check if requested web state is a popup and block it if necessary.
2790 if (!initiatedByUser) {
2791 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2792 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102793 // It's possible for a page to inject a popup into a window created via
2794 // window.open before its initial load is committed. Rather than relying
2795 // on the last committed or pending NavigationItem's referrer policy, just
2796 // use ReferrerPolicyDefault.
2797 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2798 // policy once referrer policies are correctly recorded in
2799 // NavigationItems.
2800 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512801 helper->HandlePopup(URL, referrer);
2802 return nil;
2803 }
2804 }
2805
2806 // Requested web state should not be blocked from opening.
2807 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2808 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2809
2810 // Tabs open by DOM are always renderer initiated.
2811 web::NavigationManager::WebLoadParams params(GURL{});
2812 params.transition_type = ui::PAGE_TRANSITION_LINK;
2813 params.is_renderer_initiated = true;
2814 Tab* childTab = [[self tabModel]
2815 insertTabWithLoadParams:params
2816 opener:currentTab
2817 openedByDOM:YES
2818 atIndex:TabModelConstants::kTabPositionAutomatically
2819 inBackground:NO];
2820 return childTab.webState;
2821}
2822
eugenebutb46b2122017-03-14 02:43:262823- (void)closeWebState:(web::WebState*)webState {
2824 // Only allow a web page to close itself if it was opened by DOM, or if there
2825 // are no navigation items.
2826 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582827 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262828
2829 if (![self tabModel])
2830 return;
2831
2832 NSUInteger index = [[self tabModel] indexOfTab:tab];
2833 if (index != NSNotFound)
2834 [[self tabModel] closeTabAtIndex:index];
2835}
2836
eugenebut275f5892017-03-09 22:20:512837- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552838 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2839 switch (params.disposition) {
2840 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2841 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2842 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352843 insertTabWithURL:params.url
2844 referrer:params.referrer
2845 transition:params.transition
2846 opener:LegacyTabHelper::GetTabForWebState(webState)
2847 openedByDOM:NO
2848 atIndex:TabModelConstants::kTabPositionAutomatically
2849 inBackground:(params.disposition ==
2850 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552851 return tab.webState;
2852 }
2853 case WindowOpenDisposition::CURRENT_TAB: {
2854 web::NavigationManager::WebLoadParams loadParams(params.url);
2855 loadParams.referrer = params.referrer;
2856 loadParams.transition_type = params.transition;
2857 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2858 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2859 return webState;
2860 }
eugenebutd0984e82017-02-22 23:47:512861 case WindowOpenDisposition::NEW_POPUP: {
2862 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352863 insertTabWithURL:params.url
2864 referrer:params.referrer
2865 transition:params.transition
2866 opener:LegacyTabHelper::GetTabForWebState(webState)
2867 openedByDOM:YES
2868 atIndex:TabModelConstants::kTabPositionAutomatically
2869 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512870 return tab.webState;
2871 }
eugenebut75a06fa72017-01-09 17:09:552872 default:
2873 NOTIMPLEMENTED();
2874 return nullptr;
2875 };
2876}
2877
Mike Dougherty4e6b3a32017-08-23 18:49:212878- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132879 handleContextMenu:(const web::ContextMenuParams&)params {
2880 // Prevent context menu from displaying for a tab which is no longer the
2881 // current one.
2882 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212883 return;
sdefresnee65fd872016-12-19 13:38:132884 }
2885
2886 // No custom context menu if no valid url is available in |params|.
2887 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212888 return;
sdefresnee65fd872016-12-19 13:38:132889 }
2890
2891 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132892
stkhapuginc9eee7b2017-04-10 15:49:272893 _contextMenuCoordinator =
2894 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2895 params:params];
sdefresnee65fd872016-12-19 13:38:132896
2897 NSString* title = nil;
2898 ProceduralBlock action = nil;
2899
stkhapuginc9eee7b2017-04-10 15:49:272900 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132901 GURL link = params.link_url;
2902 bool isLink = link.is_valid();
2903 GURL imageUrl = params.src_url;
2904 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192905 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132906
2907 if (isLink) {
2908 if (link.SchemeIs(url::kJavaScriptScheme)) {
2909 // Open
2910 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2911 action = ^{
2912 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2913 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2914 };
2915 [_contextMenuCoordinator addItemWithTitle:title action:action];
2916 }
2917
2918 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192919 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132920
sdefresnee65fd872016-12-19 13:38:132921 // Open in New Tab.
2922 title = l10n_util::GetNSStringWithFixup(
2923 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2924 action = ^{
2925 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002926 // The "New Tab" item in the context menu opens a new tab in the current
2927 // browser state. |isOffTheRecord| indicates whether or not the current
2928 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132929 [weakSelf webPageOrderedOpen:link
2930 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002931 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132932 inBackground:YES
2933 appendTo:kCurrentTab];
2934 };
2935 [_contextMenuCoordinator addItemWithTitle:title action:action];
2936 if (!_isOffTheRecord) {
2937 // Open in Incognito Tab.
2938 title = l10n_util::GetNSStringWithFixup(
2939 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2940 action = ^{
2941 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2942 [weakSelf webPageOrderedOpen:link
2943 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132944 inIncognito:YES
2945 inBackground:NO
2946 appendTo:kCurrentTab];
2947 };
2948 [_contextMenuCoordinator addItemWithTitle:title action:action];
2949 }
olivierrobin51d4cf42017-01-17 13:32:352950 }
gambard65d69152017-03-23 17:44:222951 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352952 NSString* innerText = params.link_text;
2953 if ([innerText length] > 0) {
2954 // Add to reading list.
2955 title = l10n_util::GetNSStringWithFixup(
2956 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2957 action = ^{
2958 Record(ACTION_READ_LATER, isImage, isLink);
2959 [weakSelf addToReadingListURL:link title:innerText];
2960 };
2961 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532962 }
sdefresnee65fd872016-12-19 13:38:132963 }
2964 // Copy Link.
2965 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2966 action = ^{
2967 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282968 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132969 };
2970 [_contextMenuCoordinator addItemWithTitle:title action:action];
2971 }
2972 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192973 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132974 // Save Image.
gambard98b4ddf2017-04-18 07:14:052975 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132976 action = ^{
2977 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2978 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2979 };
2980 [_contextMenuCoordinator addItemWithTitle:title action:action];
2981 // Open Image.
2982 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2983 action = ^{
2984 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2985 [weakSelf loadURL:imageUrl
2986 referrer:referrer
2987 transition:ui::PAGE_TRANSITION_LINK
2988 rendererInitiated:YES];
2989 };
2990 [_contextMenuCoordinator addItemWithTitle:title action:action];
2991 // Open Image In New Tab.
2992 title = l10n_util::GetNSStringWithFixup(
2993 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2994 action = ^{
2995 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2996 [weakSelf webPageOrderedOpen:imageUrl
2997 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132998 inBackground:true
2999 appendTo:kCurrentTab];
3000 };
3001 [_contextMenuCoordinator addItemWithTitle:title action:action];
3002
3003 TemplateURLService* service =
3004 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103005 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133006 if (defaultURL && !defaultURL->image_url().empty() &&
3007 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3008 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3009 defaultURL->short_name());
3010 action = ^{
3011 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3012 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3013 };
3014 [_contextMenuCoordinator addItemWithTitle:title action:action];
3015 }
3016 }
3017
3018 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133019}
3020
eugenebutb739bdc2017-01-25 06:32:483021- (void)webState:(web::WebState*)webState
3022 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3023 // Display the action sheet with the arrow pointing at the top center of the
3024 // web contents.
sdefresne0452a9d2017-02-09 15:33:283025 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483026 UIView* view = webState->GetView();
3027 CGPoint dialogLocation =
3028 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283029 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033030 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173031 helper->PresentDialog(dialogLocation,
3032 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053033 handler(shouldContinue);
3034 }));
eugenebutb739bdc2017-01-25 06:32:483035}
3036
sdefresnee65fd872016-12-19 13:38:133037- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3038 (web::WebState*)webState {
3039 return _javaScriptDialogPresenter.get();
3040}
3041
eugenebut63232102017-01-19 16:19:403042- (void)webState:(web::WebState*)webState
3043 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3044 proposedCredential:(NSURLCredential*)proposedCredential
3045 completionHandler:(void (^)(NSString* username,
3046 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:423047 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3048 if ([tab isPrerenderTab]) {
3049 [tab discardPrerender];
3050 if (handler) {
3051 handler(nil, nil);
3052 }
3053 return;
3054 }
3055
eugenebut63232102017-01-19 16:19:403056 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3057 proposedCredential:proposedCredential
3058 webState:webState
3059 completionHandler:handler];
3060}
3061
sdefresnee65fd872016-12-19 13:38:133062#pragma mark - FullScreenControllerDelegate methods
3063
3064- (CGFloat)headerOffset {
3065 if (IsIPadIdiom())
3066 return StatusBarHeight();
3067 return 0.0;
3068}
3069
stkhapugin952ecef2017-04-11 12:11:453070- (NSArray<HeaderDefinition*>*)headerViews {
3071 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133072 if (![self isViewLoaded])
3073 return results;
3074
3075 if (!IsIPadIdiom()) {
sczsf1620e52017-10-02 22:54:463076 if ([_toolbarCoordinator view]) {
stkhapugin952ecef2017-04-11 12:11:453077 [results addObject:[HeaderDefinition
sczsf1620e52017-10-02 22:54:463078 definitionWithView:[_toolbarCoordinator view]
stkhapugin952ecef2017-04-11 12:11:453079 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243080 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453081 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133082 }
3083 } else {
edchinf5150c682017-09-18 02:50:033084 if (self.tabStripView) {
3085 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3086 headerBehaviour:Hideable
3087 heightAdjustment:0.0
3088 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133089 }
sczsf1620e52017-10-02 22:54:463090 if ([_toolbarCoordinator view]) {
stkhapugin952ecef2017-04-11 12:11:453091 [results addObject:[HeaderDefinition
sczsf1620e52017-10-02 22:54:463092 definitionWithView:[_toolbarCoordinator view]
stkhapugin952ecef2017-04-11 12:11:453093 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243094 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453095 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133096 }
3097 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453098 [results addObject:[HeaderDefinition
3099 definitionWithView:[_findBarController view]
3100 headerBehaviour:Overlap
3101 heightAdjustment:0.0
3102 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133103 }
3104 }
stkhapugin952ecef2017-04-11 12:11:453105 return [results copy];
sdefresnee65fd872016-12-19 13:38:133106}
3107
3108- (UIView*)footerView {
3109 return _voiceSearchBar;
3110}
3111
3112- (CGFloat)headerHeight {
3113 return [self headerHeightForTab:[_model currentTab]];
3114}
3115
3116- (CGFloat)headerHeightForTab:(Tab*)tab {
3117 id nativeController = [self nativeControllerForTab:tab];
3118 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3119 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3120 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3121 // On iPhone, don't add any header height for ToolbarOwner native
3122 // controllers when they're displaying their own toolbar.
3123 return 0;
3124 }
3125
stkhapugin952ecef2017-04-11 12:11:453126 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133127
3128 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453129 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133130 if (header.view && header.behaviour == Hideable) {
3131 height += CGRectGetHeight([header.view frame]) -
3132 header.heightAdjustement - header.inset;
3133 }
3134 }
3135
3136 return height - StatusBarHeight();
3137}
3138
3139- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193140 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133141}
3142
3143- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453144 NSArray<HeaderDefinition*>* headers = [self headerViews];
3145 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133146 return 0.0;
3147
3148 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3149 // API documentation.
3150 if ([[[self tabModel] currentTab] isPrerenderTab])
3151 return [self headerHeight];
3152
3153 UIView* topHeader = headers[0].view;
3154 return -(topHeader.frame.origin.y - [self headerOffset]);
3155}
3156
3157- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3158 UIView* footer = [self footerView];
3159 CGFloat headerHeight = [self headerHeight];
3160 if (!footer || headerHeight == 0)
3161 return 0.0;
3162
3163 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3164 CGFloat offset = headerOffset * footerHeight / headerHeight;
3165 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3166}
3167
3168- (void)fullScreenController:(FullScreenController*)controller
3169 headerAnimationCompleted:(BOOL)completed
3170 offset:(CGFloat)offset {
3171 if (completed)
justincohen04c27772016-12-21 20:16:593172 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133173}
3174
stkhapugin952ecef2017-04-11 12:11:453175- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133176 atOffset:(CGFloat)headerOffset {
3177 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453178 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133179 CGRect frame = [header.view frame];
3180 frame.origin.y = height - headerOffset - header.inset;
3181 [header.view setFrame:frame];
3182 if (header.behaviour != Overlap)
3183 height += CGRectGetHeight(frame);
3184 }
3185}
3186
3187- (void)fullScreenController:(FullScreenController*)fullScreenController
3188 drawHeaderViewFromOffset:(CGFloat)headerOffset
3189 animate:(BOOL)animate {
3190 if ([_sideSwipeController inSwipe])
3191 return;
3192
3193 CGRect footerFrame = CGRectZero;
3194 UIView* footer = nil;
3195 // Only animate the voice search bar if the tab is a voice search results tab.
3196 if ([_model currentTab].isVoiceSearchResultsTab) {
3197 footer = [self footerView];
3198 footerFrame = footer.frame;
3199 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3200 }
3201
stkhapugin952ecef2017-04-11 12:11:453202 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133203 void (^block)(void) = ^{
3204 [self setFramesForHeaders:headers atOffset:headerOffset];
3205 footer.frame = footerFrame;
3206 };
3207 void (^completion)(BOOL) = ^(BOOL finished) {
3208 [self fullScreenController:fullScreenController
3209 headerAnimationCompleted:finished
3210 offset:headerOffset];
3211 };
3212 if (animate) {
Sylvain Defresneed8c0db2017-08-31 16:29:523213 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133214 delay:0.0
3215 options:UIViewAnimationOptionBeginFromCurrentState
3216 animations:block
3217 completion:completion];
3218 } else {
3219 block();
3220 completion(YES);
3221 }
3222}
3223
3224- (void)fullScreenController:(FullScreenController*)fullScreenController
3225 drawHeaderViewFromOffset:(CGFloat)headerOffset
3226 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3227 changeTopContentPadding:(BOOL)changeTopContentPadding
3228 scrollingToOffset:(CGFloat)contentOffset {
3229 DCHECK(webViewProxy);
3230 if ([_sideSwipeController inSwipe])
3231 return;
3232
3233 CGRect footerFrame;
3234 UIView* footer = nil;
3235 // Only animate the voice search bar if the tab is a voice search results tab.
3236 if ([_model currentTab].isVoiceSearchResultsTab) {
3237 footer = [self footerView];
3238 footerFrame = footer.frame;
3239 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3240 }
3241
stkhapugin952ecef2017-04-11 12:11:453242 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133243 void (^block)(void) = ^{
3244 [self setFramesForHeaders:headers atOffset:headerOffset];
3245 footer.frame = footerFrame;
3246 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3247 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3248 if (changeTopContentPadding)
3249 webViewProxy.topContentPadding = contentOffset;
3250 };
3251 void (^completion)(BOOL) = ^(BOOL finished) {
3252 [self fullScreenController:fullScreenController
3253 headerAnimationCompleted:finished
3254 offset:headerOffset];
3255 };
3256
Sylvain Defresneed8c0db2017-08-31 16:29:523257 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133258 delay:0.0
3259 options:UIViewAnimationOptionBeginFromCurrentState
3260 animations:block
3261 completion:completion];
3262}
3263
3264#pragma mark - VoiceSearchBarOwner
3265
3266- (id<VoiceSearchBar>)voiceSearchBar {
3267 return _voiceSearchBar;
3268}
3269
3270#pragma mark - Install OverScrollActionController method.
3271- (void)setOverScrollActionControllerToStaticNativeContent:
3272 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343273 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133274 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173275 [[OverscrollActionsController alloc]
3276 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133277 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053278 OverscrollStyle style = _isOffTheRecord
3279 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3280 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133281 controller.style = style;
3282 nativeContent.overscrollActionsController = controller;
3283 }
3284}
3285
3286#pragma mark - OverscrollActionsControllerDelegate methods.
3287
3288- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053289 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133290 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053291 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383292 [self.dispatcher
3293 openNewTab:[OpenNewTabCommand
3294 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133295 break;
rohitrao922b7111c2017-01-03 14:31:053296 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243297 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133298 break;
liaoyuke563dc4a2017-03-17 18:36:293299 case OverscrollAction::REFRESH: {
liaoyuke563dc4a2017-03-17 18:36:293300 web::WebState* webState = [_model currentTab].webState;
Eugene But083b6c7a2017-10-02 15:49:383301 if (webState) {
3302 if (webState->IsLoading()) {
3303 webState->Stop();
3304 }
liaoyuke563dc4a2017-03-17 18:36:293305 // |check_for_repost| is true because the reload is explicitly initiated
3306 // by the user.
3307 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3308 true /* check_for_repost */);
Eugene But083b6c7a2017-10-02 15:49:383309 }
sdefresnee65fd872016-12-19 13:38:133310 break;
liaoyuke563dc4a2017-03-17 18:36:293311 }
rohitrao922b7111c2017-01-03 14:31:053312 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133313 NOTREACHED();
3314 break;
3315 }
3316}
3317
3318- (BOOL)shouldAllowOverscrollActions {
3319 return YES;
3320}
3321
3322- (UIView*)headerView {
sczsf1620e52017-10-02 22:54:463323 return [_toolbarCoordinator view];
sdefresnee65fd872016-12-19 13:38:133324}
3325
3326- (UIView*)toolbarSnapshotView {
sczsf1620e52017-10-02 22:54:463327 return [[_toolbarCoordinator view] snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133328}
3329
3330- (CGFloat)overscrollActionsControllerHeaderInset:
3331 (OverscrollActionsController*)controller {
3332 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3333 return [self headerHeight];
3334 else
3335 return 0;
3336}
3337
3338- (CGFloat)overscrollHeaderHeight {
3339 return [self headerHeight] + StatusBarHeight();
3340}
3341
3342#pragma mark - TabSnapshottingDelegate methods.
3343
3344- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3345 CGRect pageContentArea = _contentArea.bounds;
3346 if ([_model webUsageEnabled])
3347 pageContentArea = tab.view.bounds;
3348 CGFloat headerHeight = [self headerHeightForTab:tab];
3349 id nativeController = [self nativeControllerForTab:tab];
3350 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3351 headerHeight += [nativeController toolbarHeight];
3352 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3353 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3354}
3355
3356#pragma mark - NewTabPageObserver methods.
3357
3358- (void)selectedPanelDidChange {
3359 [self updateToolbar];
3360}
3361
3362#pragma mark - CRWNativeContentProvider methods
3363
3364- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3365 withError:(NSError*)error
3366 isPost:(BOOL)isPost {
3367 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173368 [[ErrorPageContent alloc] initWithLoader:self
3369 browserState:self.browserState
3370 url:url
3371 error:error
3372 isPost:isPost
3373 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133374 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3375 return errorPageContent;
3376}
3377
3378- (BOOL)hasControllerForURL:(const GURL&)url {
3379 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453380 if (host == kChromeUIOfflineHost) {
3381 // Only allow offline URL that are fully specified.
3382 return reading_list::IsOfflineURLValid(
3383 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3384 }
sdefresnee65fd872016-12-19 13:38:133385
Justin Cohen8679e852017-08-14 16:35:253386 if (host == kChromeUIBookmarksHost) {
Marti Wong2a43bb92017-10-10 12:55:243387 // Only allow bookmark URL on iPad when Bookmarks is not shown modally.
3388 return IsIPadIdiom() && !PresentNTPPanelModally();
Justin Cohen8679e852017-08-14 16:35:253389 }
3390
3391 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133392}
3393
olivierrobind43eecb2017-01-27 20:35:263394- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3395 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133396 DCHECK(url.SchemeIs(kChromeUIScheme));
3397
3398 id<CRWNativeContent> nativeController = nil;
3399 std::string url_host = url.host();
Justin Cohen49715952017-08-22 14:12:193400 if (url_host == kChromeUINewTabHost ||
Marti Wong2a43bb92017-10-10 12:55:243401 (IsIPadIdiom() && url_host == kChromeUIBookmarksHost &&
3402 !PresentNTPPanelModally())) {
Gauthier Ambardd8890452017-09-29 12:07:463403 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3404 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3405 if (@available(iOS 11.0, *)) {
3406 safeAreaInset = self.view.safeAreaInsets;
3407 }
3408 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3409
sdefresnee65fd872016-12-19 13:38:133410 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173411 [[NewTabPageController alloc] initWithUrl:url
3412 loader:self
sczsf1620e52017-10-02 22:54:463413 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173414 ntpObserver:self
3415 browserState:_browserState
3416 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463417 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453418 tabModel:_model
justincohen75011c32017-04-28 16:31:393419 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463420 dispatcher:self.dispatcher
3421 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133422 pageController.swipeRecognizerProvider = self.sideSwipeController;
3423
3424 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233425 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133426
Marti Wong2a43bb92017-10-10 12:55:243427 if (IsIPadIdiom() && !PresentNTPPanelModally()) {
sdefresnee65fd872016-12-19 13:38:133428 // New Tab Page can have multiple panels. Each panel is addressable
3429 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3430 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3431 // the Bookmark Manager, etc.
3432 // The utility functions NewTabPage::IdentifierFromFragment() and
3433 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3434 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3435 // without changing the URL since the URL may be chrome://bookmarks/#123.
3436 // If the URL is chrome://newtab/, pre-select the panel based on the
3437 // #fragment.
3438 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233439 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133440 : NewTabPage::IdentifierFromFragment(url.ref());
3441 }
3442 [pageController selectPanel:panelType];
3443 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453444 } else if (url_host == kChromeUIOfflineHost &&
3445 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133446 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173447 [[OfflinePageNativeContent alloc] initWithLoader:self
3448 browserState:_browserState
3449 webState:webState
3450 URL:url];
sdefresnee65fd872016-12-19 13:38:133451 [self setOverScrollActionControllerToStaticNativeContent:
3452 staticNativeController];
3453 nativeController = staticNativeController;
3454 } else if (url_host == kChromeUIExternalFileHost) {
3455 // Return an instance of the |ExternalFileController| only if the file is
3456 // still in the sandbox.
3457 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3458 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173459 nativeController =
3460 [[ExternalFileController alloc] initWithURL:url
3461 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133462 }
peterlaurens44615d02017-05-23 20:23:093463 } else if (url_host == kChromeUICrashHost) {
3464 // There is no native controller for kChromeUICrashHost, it is instead
3465 // handled as any other renderer crash by the SadTabTabHelper.
3466 // nativeController must be set to nil to prevent defaulting to a
3467 // PageNotAvailableController.
3468 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133469 } else {
3470 DCHECK(![self hasControllerForURL:url]);
3471 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173472 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133473 }
3474 // If a native controller is vended before its tab is added to the tab model,
3475 // use the temporary key and add it under the new tab's tabId in the
3476 // TabModelObserver callback. This happens:
3477 // - when there is no current tab (occurs when vending the NTP controller for
3478 // the first tab that is opened),
3479 // - when the current tab's url doesn't match |url| (occurs when a native
3480 // controller is opened in a new tab)
3481 // - when the current tab's url matches |url| and there is already a native
3482 // controller of the appropriate type vended to it (occurs when a native
3483 // controller is opened in a new tab from a tab with a matching URL, e.g.
3484 // opening an NTP when an NTP is already displayed in the current tab).
3485 // For normal page loads, history navigations, tab restorations, and crash
3486 // recoveries, the tab will already exist in the tab model and the tabId can
3487 // be used as the native controller key.
3488 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3489 // that native controllers vended here always correspond to the current tab.
3490 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193491 if (!currentTab.webState ||
3492 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443493 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133494 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443495 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133496 }
sdefresnee65fd872016-12-19 13:38:133497 return nativeController;
3498}
3499
3500- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443501 id nativeController = tab.webController.nativeController;
3502 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133503}
3504
3505#pragma mark - DialogPresenterDelegate methods
3506
3507- (void)dialogPresenter:(DialogPresenter*)presenter
3508 willShowDialogForWebState:(web::WebState*)webState {
3509 for (Tab* iteratedTab in self.tabModel) {
3510 if ([iteratedTab webState] == webState) {
3511 self.tabModel.currentTab = iteratedTab;
3512 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3513 break;
3514 }
3515 }
3516}
3517
3518#pragma mark - Context menu methods
3519
3520- (void)searchByImageAtURL:(const GURL&)url
3521 referrer:(const web::Referrer)referrer {
3522 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273523 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113524 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173525 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3526 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113527 DCHECK(data);
3528 dispatch_async(dispatch_get_main_queue(), ^{
3529 [weakSelf searchByImageData:data atURL:image_source_url];
3530 });
3531 };
3532 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133533 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3534 web::PolicyForNavigation(url, referrer));
3535}
3536
3537- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3538 NSData* imageData = data;
3539 UIImage* image = [UIImage imageWithData:imageData];
3540 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3541 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3542 // kSearchByImageMaxImageHeight).
3543 if (image &&
3544 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3545 (image.size.width > kSearchByImageMaxImageWidth ||
3546 image.size.height > kSearchByImageMaxImageHeight)) {
3547 CGSize newImageSize =
3548 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3549 image = [image gtm_imageByResizingToSize:newImageSize
3550 preserveAspectRatio:YES
3551 trimToFit:NO];
3552 imageData = UIImageJPEGRepresentation(image, 1.0);
3553 }
3554
3555 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3556 std::string byteString(bytes, [imageData length]);
3557
3558 TemplateURLService* templateUrlService =
3559 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103560 const TemplateURL* defaultURL =
3561 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133562 DCHECK(!defaultURL->image_url().empty());
3563 DCHECK(defaultURL->image_url_ref().IsValid(
3564 templateUrlService->search_terms_data()));
3565 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3566 search_args.image_url = imageURL;
3567 search_args.image_thumbnail_content = byteString;
3568
3569 // Generate the URL and populate |post_content| with the content type and
3570 // HTTP body for the request.
3571 TemplateURLRef::PostContent post_content;
3572 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3573 search_args, templateUrlService->search_terms_data(), &post_content));
3574 [self addSelectedTabWithURL:result
3575 postData:&post_content
3576 transition:ui::PAGE_TRANSITION_TYPED];
3577}
3578
3579- (void)saveImageAtURL:(const GURL&)url
3580 referrer:(const web::Referrer&)referrer {
3581 DCHECK(url.is_valid());
3582
gambard9efce7a2017-02-09 18:53:173583 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3584 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113585 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133586
gambardbbf85c42017-06-29 11:15:343587 if ([data length] == 0) {
3588 [self displayPrivacyErrorAlertOnMainQueue:
3589 l10n_util::GetNSString(
3590 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3591 return;
3592 }
3593
gambard9efce7a2017-02-09 18:53:173594 base::FilePath::StringType extension;
3595
3596 bool extensionSuccess =
3597 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3598 if (!extensionSuccess || extension.length() == 0) {
3599 extension = "png";
3600 }
3601
3602 NSString* fileExtension =
3603 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3604 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113605 };
3606 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133607 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3608 web::PolicyForNavigation(url, referrer));
3609}
3610
gambard9efce7a2017-02-09 18:53:173611- (void)managePermissionAndSaveImage:(NSData*)data
3612 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133613 switch ([PHPhotoLibrary authorizationStatus]) {
3614 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173615 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133616 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3617 // Call -saveImage again to check if chrome needs to display an error or
3618 // saves the image.
3619 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173620 [self managePermissionAndSaveImage:data
3621 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133622 }];
3623 break;
stkhapuginf58b10d02017-04-10 13:36:173624 }
sdefresnee65fd872016-12-19 13:38:133625
3626 // The application doesn't have permission to access photo and the user
3627 // cannot grant it.
3628 case PHAuthorizationStatusRestricted:
3629 [self displayPrivacyErrorAlertOnMainQueue:
3630 l10n_util::GetNSString(
3631 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3632 break;
3633
3634 // The application doesn't have permission to access photo and the user
3635 // can grant it.
3636 case PHAuthorizationStatusDenied:
3637 [self displayImageErrorAlertWithSettingsOnMainQueue];
3638 break;
3639
3640 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243641 default:
3642 __weak BrowserViewController* weakSelf = self;
3643 [self saveImage:data
3644 withFileExtension:fileExtension
3645 completion:^(BOOL success, NSError* error) {
3646 [weakSelf finishSavingImageWithError:error];
3647 }];
sdefresnee65fd872016-12-19 13:38:133648 break;
sdefresnee65fd872016-12-19 13:38:133649 }
3650}
3651
Sylvain Defresnefd3ecf22017-07-12 18:47:243652- (void)saveImage:(NSData*)data
3653 withFileExtension:(NSString*)fileExtension
3654 completion:(void (^)(BOOL, NSError*))completion {
3655 base::PostTaskWithTraits(
3656 FROM_HERE,
3657 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3658 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3659 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373660 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133661
Sylvain Defresnefd3ecf22017-07-12 18:47:243662 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3663 stringByAppendingString:fileExtension];
3664 NSURL* fileURL = [NSURL
3665 fileURLWithPath:[NSTemporaryDirectory()
3666 stringByAppendingPathComponent:fileName]];
3667 NSError* error = nil;
3668 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3669 if (error) {
3670 if (completion)
3671 completion(NO, error);
3672 return;
3673 }
sdefresnee65fd872016-12-19 13:38:133674
Sylvain Defresnefd3ecf22017-07-12 18:47:243675 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3676 [PHAssetChangeRequest
3677 creationRequestForAssetFromImageAtFileURL:fileURL];
3678 }
3679 completionHandler:^(BOOL success, NSError* error) {
3680 base::PostTaskWithTraits(
3681 FROM_HERE,
3682 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3683 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3684 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373685 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243686 if (completion)
3687 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133688
Sylvain Defresnefd3ecf22017-07-12 18:47:243689 // Cleanup the temporary file.
3690 NSError* deleteFileError = nil;
3691 [[NSFileManager defaultManager]
3692 removeItemAtURL:fileURL
3693 error:&deleteFileError];
3694 }));
3695 }];
3696 }));
sdefresnee65fd872016-12-19 13:38:133697}
3698
3699- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3700 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3701 BOOL canGoToSetting =
3702 [[UIApplication sharedApplication] canOpenURL:settingURL];
3703 if (canGoToSetting) {
3704 dispatch_async(dispatch_get_main_queue(), ^{
3705 [self displayImageErrorAlertWithSettings:settingURL];
3706 });
3707 } else {
3708 [self displayPrivacyErrorAlertOnMainQueue:
3709 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3710 }
3711}
3712
3713- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3714 // Dismiss current alert.
3715 [_alertCoordinator stop];
3716
3717 NSString* title =
3718 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3719 NSString* message = l10n_util::GetNSString(
3720 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3721
stkhapuginc9eee7b2017-04-10 15:49:273722 _alertCoordinator =
3723 [[AlertCoordinator alloc] initWithBaseViewController:self
3724 title:title
3725 message:message];
sdefresnee65fd872016-12-19 13:38:133726
3727 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3728 action:nil
3729 style:UIAlertActionStyleCancel];
3730
3731 [_alertCoordinator
3732 addItemWithTitle:l10n_util::GetNSString(
3733 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3734 action:^{
3735 OpenUrlWithCompletionHandler(settingURL, nil);
3736 }
3737 style:UIAlertActionStyleDefault];
3738
3739 [_alertCoordinator start];
3740}
3741
3742- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3743 dispatch_async(dispatch_get_main_queue(), ^{
3744 NSString* title =
3745 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3746 [self showErrorAlertWithStringTitle:title message:errorContent];
3747 });
3748}
3749
3750// This callback is triggered when the image is effectively saved onto the photo
3751// album, or if the save failed for some reason.
3752- (void)finishSavingImageWithError:(NSError*)error {
3753 // Was there an error?
3754 if (error) {
3755 // Saving photo failed even though user has granted access to Photos.
3756 // Display the error information from the NSError object for user.
3757 NSString* errorMessage = [NSString
3758 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3759 [error domain], [error code]];
3760 // This code may be execute outside of the main thread. Make sure to display
3761 // the error on the main thread.
3762 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3763 } else {
3764 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3765 // the photo application. The current behaviour is to create the photo there
3766 // but not providing any link to it is suboptimal. That's what Safari is
3767 // doing, and what the PM want, but it doesn't make it right.
3768 }
3769}
3770
sdefresnee65fd872016-12-19 13:38:133771#pragma mark - Showing popups
3772
sdefresnee65fd872016-12-19 13:38:133773- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133774 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3775
3776 ReadingListModel* readingModel =
3777 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013778 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3779 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133780
pinkerton07e27842017-03-02 15:29:023781 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363782 [self showSnackbar:l10n_util::GetNSString(
3783 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133784}
3785
3786#pragma mark - Keyboard commands management
3787
3788- (BOOL)shouldRegisterKeyboardCommands {
3789 if ([self presentedViewController])
3790 return NO;
3791
3792 if (_voiceSearchController && _voiceSearchController->IsVisible())
3793 return NO;
3794
3795 // If there is no first responder, try to make the webview the first
3796 // responder.
3797 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303798 web::WebState* webState = _model.currentTab.webState;
3799 if (webState)
3800 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133801 }
3802
3803 return YES;
3804}
3805
3806- (KeyCommandsProvider*)keyCommandsProvider {
3807 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273808 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133809 }
stkhapuginc9eee7b2017-04-10 15:49:273810 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133811}
3812
3813#pragma mark - KeyCommandsPlumbing
3814
3815- (BOOL)isOffTheRecord {
3816 return _isOffTheRecord;
3817}
3818
3819- (NSUInteger)tabsCount {
3820 return [_model count];
3821}
3822
lpromero47ea8862017-01-13 17:51:063823- (BOOL)canGoBack {
3824 return [_model currentTab].canGoBack;
3825}
3826
3827- (BOOL)canGoForward {
3828 return [_model currentTab].canGoForward;
3829}
3830
sdefresnee65fd872016-12-19 13:38:133831- (void)focusTabAtIndex:(NSUInteger)index {
3832 if ([_model count] > index) {
3833 [_model setCurrentTab:[_model tabAtIndex:index]];
3834 }
3835}
3836
3837- (void)focusNextTab {
3838 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3839 NSInteger modelCount = [_model count];
3840 if (currentTabIndex < modelCount - 1) {
3841 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3842 [_model setCurrentTab:nextTab];
3843 } else {
3844 [_model setCurrentTab:[_model tabAtIndex:0]];
3845 }
3846}
3847
3848- (void)focusPreviousTab {
3849 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3850 if (currentTabIndex > 0) {
3851 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3852 [_model setCurrentTab:previousTab];
3853 } else {
3854 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3855 [_model setCurrentTab:lastTab];
3856 }
3857}
3858
3859- (void)reopenClosedTab {
3860 sessions::TabRestoreService* const tabRestoreService =
3861 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3862 if (!tabRestoreService || tabRestoreService->entries().empty())
3863 return;
3864
3865 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3866 tabRestoreService->entries().front();
3867 // Only handle the TAB type.
3868 if (entry->type != sessions::TabRestoreService::TAB)
3869 return;
3870
Mark Cogandfcdea72017-07-18 13:47:383871 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133872 TabRestoreServiceDelegateImplIOS* const delegate =
3873 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3874 _browserState);
3875 tabRestoreService->RestoreEntryById(delegate, entry->id,
3876 WindowOpenDisposition::CURRENT_TAB);
3877}
3878
3879- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463880 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133881}
3882
3883#pragma mark - UIResponder
3884
3885- (NSArray*)keyCommands {
3886 if (![self shouldRegisterKeyboardCommands]) {
3887 return nil;
3888 }
3889 return [self.keyCommandsProvider
3890 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543891 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243892 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133893 editingText:![self isFirstResponder]];
3894}
3895
3896#pragma mark -
3897
3898// Induce an intentional crash in the browser process.
3899- (void)induceBrowserCrash {
3900 CHECK(false);
3901 // Call another function, so that the above CHECK can't be tail-call
3902 // optimized. This ensures that this method's name will show up in the stack
3903 // for easier identification.
3904 CHECK(true);
3905}
3906
3907- (void)loadURL:(const GURL&)url
3908 referrer:(const web::Referrer&)referrer
3909 transition:(ui::PageTransition)transition
3910 rendererInitiated:(BOOL)rendererInitiated {
3911 [[OmniboxGeolocationController sharedInstance]
3912 locationBarDidSubmitURL:url
3913 transition:transition
3914 browserState:_browserState];
3915
3916 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3917 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3918 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3919 }
3920
3921 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243922 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133923 // restart.
3924 if (url.host() == kChromeUIBrowserCrashHost) {
3925 [self induceBrowserCrash];
3926 // In debug the app can continue working even after the CHECK. Adding a
3927 // return avoids the crash url to be added to the history.
3928 return;
3929 }
3930
Danyao Wang85389a82017-10-25 18:56:273931 bool typed_or_generated_transition =
3932 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3933 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3934
Rohit Rao44f204302017-08-10 14:49:543935 PrerenderService* prerenderService =
3936 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3937 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593938 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543939 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593940 DCHECK(newWebState);
3941
sdefresnee65fd872016-12-19 13:38:133942 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593943 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133944 DCHECK(oldTab);
3945 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593946
kkhorimotod804c5732017-03-15 23:44:523947 bool canPruneItems =
3948 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593949
kkhorimotod804c5732017-03-15 23:44:523950 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523951 [newTab navigationManager]->CopyStateFromAndPrune(
3952 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593953
3954 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3955 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133956
3957 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3958 // BrowserViewController to detect that a pre-rendered tab is switched in,
3959 // and show the prerendering animation.
3960 newTab.isPrerenderTab = NO;
Danyao Wang85389a82017-10-25 18:56:273961 if (typed_or_generated_transition) {
3962 LoadTimingTabHelper::FromWebState(newTab.webState)
3963 ->DidPromotePrerenderTab();
3964 }
sdefresnee65fd872016-12-19 13:38:133965
sdefresne2f7781c2017-03-02 19:12:463966 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133967 return;
3968 }
3969 }
3970
3971 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543972 if (prerenderService) {
3973 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133974 }
3975
sdefresnee65fd872016-12-19 13:38:133976 // Some URLs are not allowed while in incognito. If we are in incognito and
3977 // load a disallowed URL, instead create a new tab not in the incognito state.
3978 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3979 [self webPageOrderedOpen:url
3980 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133981 inIncognito:NO
3982 inBackground:NO
3983 appendTo:kCurrentTab];
3984 return;
3985 }
3986
Danyao Wang85389a82017-10-25 18:56:273987 if (typed_or_generated_transition) {
3988 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
3989 ->DidInitiatePageLoad();
3990 }
3991
mrefaata84d5a02017-06-08 17:13:293992 // If this is a reload initiated from the omnibox.
3993 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3994 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3995 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3996 [[_model currentTab] navigationManager]->Reload(
3997 web::ReloadType::NORMAL, true /* check_for_repost */);
3998 return;
3999 }
4000
sdefresnee65fd872016-12-19 13:38:134001 web::NavigationManager::WebLoadParams params(urlToLoad);
4002 params.referrer = referrer;
4003 params.transition_type = transition;
4004 params.is_renderer_initiated = rendererInitiated;
4005 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:234006 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134007}
4008
4009- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544010 PrerenderService* prerenderService =
4011 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4012 if (prerenderService) {
4013 prerenderService->CancelPrerender();
4014 }
sdefresnee65fd872016-12-19 13:38:134015 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184016 if ([self currentWebState])
4017 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134018}
4019
4020- (web::WebState*)currentWebState {
4021 return [[_model currentTab] webState];
4022}
4023
sdefresnee65fd872016-12-19 13:38:134024// Load a new URL on a new page/tab.
4025- (void)webPageOrderedOpen:(const GURL&)URL
4026 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134027 inBackground:(BOOL)inBackground
4028 appendTo:(OpenPosition)appendTo {
4029 Tab* adjacentTab = nil;
4030 if (appendTo == kCurrentTab)
4031 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354032 [_model insertTabWithURL:URL
4033 referrer:referrer
4034 transition:ui::PAGE_TRANSITION_LINK
4035 opener:adjacentTab
4036 openedByDOM:NO
4037 atIndex:TabModelConstants::kTabPositionAutomatically
4038 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134039}
4040
4041- (void)webPageOrderedOpen:(const GURL&)url
4042 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134043 inIncognito:(BOOL)inIncognito
4044 inBackground:(BOOL)inBackground
4045 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004046 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234047 // feature_engagement::Tracker based on |inIncognito|.
4048 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004049
sdefresnee65fd872016-12-19 13:38:134050 if (inIncognito == _isOffTheRecord) {
4051 [self webPageOrderedOpen:url
4052 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134053 inBackground:inBackground
4054 appendTo:appendTo];
4055 return;
4056 }
4057 // When sending an open command that switches modes, ensure the tab
4058 // ends up appended to the end of the model, not just next to what is
4059 // currently selected in the other mode. This is done with the |append|
4060 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274061 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134062 initWithURL:url
4063 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134064 inIncognito:inIncognito
4065 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274066 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154067 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134068}
4069
4070- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054071 WebStateList* webStateList = [_model webStateList];
4072 webStateList->ReplaceWebStateAt(
4073 webStateList->active_index(),
4074 session_util::CreateWebStateWithNavigationEntries(
4075 [_model browserState], sessionTab->current_navigation_index,
4076 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134077}
4078
4079- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434080 DCHECK(javascript);
4081 javascript = [javascript stringByRemovingPercentEncoding];
4082 web::WebState* webState = [[_model currentTab] webState];
4083 if (webState) {
4084 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4085 }
sdefresnee65fd872016-12-19 13:38:134086}
4087
4088#pragma mark - WebToolbarDelegate methods
4089
4090- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
4091 if (_locationBarHasFocus)
4092 return; // TODO(crbug.com/244366): This should not be necessary.
4093 _locationBarHasFocus = YES;
4094 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524095 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134096 object:nil];
4097 [_sideSwipeController setEnabled:NO];
4098 if ([[_model currentTab].webController wantsKeyboardShield]) {
4099 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4100 [_typingShield setAlpha:0.0];
4101 [_typingShield setHidden:NO];
4102 [UIView animateWithDuration:0.3
4103 animations:^{
4104 [_typingShield setAlpha:1.0];
4105 }];
4106 }
4107 [[OmniboxGeolocationController sharedInstance]
4108 locationBarDidBecomeFirstResponder:_browserState];
4109}
4110
4111- (IBAction)locationBarDidResignFirstResponder:(id)sender {
4112 if (!_locationBarHasFocus)
4113 return; // TODO(crbug.com/244366): This should not be necessary.
4114 _locationBarHasFocus = NO;
4115 [_sideSwipeController setEnabled:YES];
4116 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524117 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134118 object:nil];
4119 [UIView animateWithDuration:0.3
4120 animations:^{
4121 [_typingShield setAlpha:0.0];
4122 }
4123 completion:^(BOOL finished) {
4124 // This can happen if one quickly resigns the omnibox and then taps
4125 // on the omnibox again during this animation. If the animation is
4126 // interrupted and the toolbar controller is first responder, it's safe
4127 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464128 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134129 return;
4130 [_typingShield setHidden:YES];
4131 }];
4132 [[OmniboxGeolocationController sharedInstance]
4133 locationBarDidResignFirstResponder:_browserState];
4134
4135 // If a load was cancelled by an omnibox edit, but nothing is loading when
4136 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4137 if (_locationBarEditCancelledLoad) {
4138 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294139
4140 web::WebState* webState = [_model currentTab].webState;
4141 if (!_toolbarModelIOS->IsLoading() && webState)
4142 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4143 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134144 }
4145}
4146
4147- (IBAction)locationBarBeganEdit:(id)sender {
4148 // On handsets, if a page is currently loading it should be stopped.
4149 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354150 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134151 _locationBarEditCancelledLoad = YES;
4152 }
4153}
4154
sdefresnee65fd872016-12-19 13:38:134155- (ToolbarModelIOS*)toolbarModelIOS {
4156 return _toolbarModelIOS.get();
4157}
4158
sdefresnee65fd872016-12-19 13:38:134159- (void)willUpdateToolbarSnapshot {
4160 [[_model currentTab].overscrollActionsController clear];
4161}
4162
4163- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4164 CGRect frame = [_contentArea frame];
4165 if (!fullScreen) {
4166 // Changing the origin here is unnecessary, it's set in page_animation_util.
4167 frame.size.height -= [self headerHeight];
4168 }
4169
4170 CGFloat shortAxis = frame.size.width;
4171 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524172 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134173 CGFloat aspectRatio = frame.size.height / frame.size.width;
4174 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4175 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4176 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4177 CGRect cardFrame = {frame.origin, cardSize};
4178
4179 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174180 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134181 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4182 : CardCloseButtonSide::LEADING;
4183 [_contentArea addSubview:card];
4184 return card;
4185}
4186
Mark Cogan6ebbde02017-07-07 12:50:134187#pragma mark - BrowserCommands
4188
4189- (void)goBack {
4190 [[_model currentTab] goBack];
4191}
4192
4193- (void)goForward {
4194 [[_model currentTab] goForward];
4195}
4196
Mark Coganb9aac6432017-07-07 13:26:354197- (void)stopLoading {
4198 [_model currentTab].webState->Stop();
4199}
4200
4201- (void)reload {
4202 web::WebState* webState = [_model currentTab].webState;
4203 if (webState) {
4204 // |check_for_repost| is true because the reload is explicitly initiated
4205 // by the user.
4206 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4207 true /* check_for_repost */);
4208 }
4209}
4210
Mark Cogan8e791022017-07-10 09:55:354211- (void)bookmarkPage {
4212 [self initializeBookmarkInteractionController];
4213 [_bookmarkInteractionController
4214 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064215 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354216}
4217
Mark Cogan6acee7f2017-07-11 09:01:404218- (void)showToolsMenu {
4219 DCHECK(_browserState);
4220 DCHECK(self.visible || self.dismissingModal);
4221
4222 // Record the time this menu was requested; to be stored in the configuration
4223 // object.
4224 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4225
4226 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:464227 [_toolbarCoordinator cancelOmniboxEdit];
Mark Cogan6acee7f2017-07-11 09:01:404228 // Dismiss the soft keyboard (if open).
4229 [[_model currentTab].webController dismissKeyboard];
4230 // Dismiss Find in Page focus.
4231 [self updateFindBar:NO shouldFocus:NO];
4232
4233 ToolsMenuConfiguration* configuration =
edchin8e4cfe032017-10-25 13:25:544234 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4235 baseViewController:self];
Mark Cogan6acee7f2017-07-11 09:01:404236 configuration.requestStartTime =
4237 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4238 if ([_model count] == 0)
4239 [configuration setNoOpenedTabs:YES];
4240
4241 if (_isOffTheRecord)
4242 [configuration setInIncognito:YES];
4243
4244 if (!_readingListMenuNotifier) {
4245 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4246 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4247 _browserState)];
4248 }
Cooper Knaake4f495cf2017-07-27 23:30:034249
4250 feature_engagement::Tracker* engagementTracker =
4251 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4252 if (engagementTracker->ShouldTriggerHelpUI(
4253 feature_engagement::kIPHBadgedReadingListFeature)) {
4254 [configuration setShowReadingListNewBadge:YES];
4255 [configuration setEngagementTracker:engagementTracker];
4256 }
Mark Cogan6acee7f2017-07-11 09:01:404257 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4258
4259 [configuration setUserAgentType:self.userAgentType];
4260
Helen Yang9175bd52017-08-12 00:28:404261 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4262 [configuration setHighlightNewIncognitoTabCell:YES];
4263 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4264 }
4265
4266 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4267 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4268 }
4269
sczsf1620e52017-10-02 22:54:464270 [_toolbarCoordinator showToolsMenuPopupWithConfiguration:configuration];
Mark Cogan6acee7f2017-07-11 09:01:404271
4272 ToolsPopupController* toolsPopupController =
sczsf1620e52017-10-02 22:54:464273 [_toolbarCoordinator toolsPopupController];
Mark Cogan6acee7f2017-07-11 09:01:404274 if ([_model currentTab]) {
4275 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4276 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4277 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404278 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4279
4280 if (!IsIPadIdiom())
4281 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4282 }
4283}
4284
Mark Cogandfcdea72017-07-18 13:47:384285- (void)openNewTab:(OpenNewTabCommand*)command {
4286 if (self.isOffTheRecord != command.incognito) {
4287 // Not for this browser state, send it on its way.
4288 [self.dispatcher switchModesAndOpenNewTab:command];
4289 return;
4290 }
4291
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004292 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234293 // events to the feature_engagement::Tracker based on |command.userInitiated|
4294 // and |command.incognito|.
4295 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004296
Mark Cogandfcdea72017-07-18 13:47:384297 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4298 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494299 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4300 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384301 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494302 if (oldForegroundTabWasAddedCompletionBlock) {
4303 oldForegroundTabWasAddedCompletionBlock();
4304 }
Mark Cogandfcdea72017-07-18 13:47:384305 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4306 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4307 if (offTheRecord) {
4308 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4309 timeDelta);
4310 } else {
4311 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4312 }
4313 };
4314
4315 [self setLastTapPoint:command];
4316 DCHECK(self.visible || self.dismissingModal);
4317 Tab* currentTab = [_model currentTab];
4318 if (currentTab) {
4319 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4320 }
4321 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4322 transition:ui::PAGE_TRANSITION_TYPED];
4323}
4324
Mark Cogan123895002017-07-20 12:54:064325- (void)printTab {
4326 Tab* currentTab = [_model currentTab];
4327 // The UI should prevent users from printing non-printable pages. However, a
4328 // redirection to an un-printable page can happen before it is reflected in
4329 // the UI.
4330 if (![currentTab viewForPrinting]) {
4331 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364332 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064333 return;
4334 }
4335 DCHECK(_browserState);
4336 if (!_printController) {
4337 _printController = [[PrintController alloc]
4338 initWithContextGetter:_browserState->GetRequestContext()];
4339 }
4340 [_printController printView:[currentTab viewForPrinting]
4341 withTitle:[currentTab title]
4342 viewController:self];
4343}
4344
Mark Coganfa25b052017-07-20 17:31:034345- (void)addToReadingList:(ReadingListAddCommand*)command {
4346 [self addToReadingListURL:[command URL] title:[command title]];
4347}
4348
sczs3a8c8602017-08-01 20:14:084349- (void)showReadingList {
4350 _readingListCoordinator = [[ReadingListCoordinator alloc]
4351 initWithBaseViewController:self
4352 browserState:self.browserState
4353 loader:self];
4354
4355 [_readingListCoordinator start];
4356}
4357
Jean-François Geyelinedef9552017-08-07 09:56:564358- (void)preloadVoiceSearch {
4359 // Preload VoiceSearchController and views and view controllers needed
4360 // for voice search.
4361 [self ensureVoiceSearchControllerCreated];
4362 _voiceSearchController->PrepareToAppear();
4363}
4364
edchinc5720722017-08-14 22:06:314365#if !defined(NDEBUG)
4366- (void)viewSource {
4367 Tab* tab = [_model currentTab];
4368 DCHECK(tab);
4369 CRWWebController* webController = tab.webController;
4370 NSString* script = @"document.documentElement.outerHTML;";
4371 __weak Tab* weakTab = tab;
4372 __weak BrowserViewController* weakSelf = self;
4373 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4374 Tab* strongTab = weakTab;
4375 if (!strongTab)
4376 return;
4377 if (![result isKindOfClass:[NSString class]])
4378 result = @"Not an HTML page";
4379 std::string base64HTML;
4380 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4381 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194382 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314383 web::ReferrerPolicyDefault);
4384
4385 [[weakSelf tabModel]
4386 insertTabWithURL:URL
4387 referrer:referrer
4388 transition:ui::PAGE_TRANSITION_LINK
4389 opener:strongTab
4390 openedByDOM:YES
4391 atIndex:TabModelConstants::kTabPositionAutomatically
4392 inBackground:NO];
4393 };
4394 [webController executeJavaScript:script
4395 completionHandler:completionHandlerBlock];
4396}
4397#endif // !defined(NDEBUG)
4398
edchin2134c042017-08-18 13:57:354399// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4400- (void)showRateThisAppDialog {
4401 DCHECK(!_rateThisAppDialog);
4402
4403 // Store the current timestamp whenever this dialog is shown.
4404 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4405 base::Time::Now().ToInternalValue());
4406
Gregory Chatzinofff39ec5162017-10-05 20:28:534407 // iOS11 no longer supports the itms link to the app store. So, use a deep
4408 // link for iOS11 and the itms link for prior versions.
4409 NSURL* storeURL;
4410 if (base::ios::IsRunningOnIOS11OrLater()) {
4411 storeURL =
4412 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4413 @"google-chrome-the-fast-and-secure-web-browser/"
4414 @"id535886823?action=write-review")];
4415 } else {
4416 storeURL = [NSURL
4417 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4418 @"MZStore.woa/wa/"
4419 @"viewContentsUserReviews?type=Purple+Software&id="
4420 @"535886823&pt=9008&ct=rating")];
4421 }
edchin2134c042017-08-18 13:57:354422
4423 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014424 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354425
4426 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4427 [_rateThisAppDialog setAppStoreURL:storeURL];
4428 [_rateThisAppDialog setDelegate:self];
4429 [_rateThisAppDialog show];
4430}
4431
Gregory Chatzinoff3f40c1542017-08-30 07:50:044432- (void)showFindInPage {
4433 if (!self.canShowFindBar)
4434 return;
4435
4436 if (!_findBarController) {
4437 _findBarController =
4438 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4439 _findBarController.dispatcher = self.dispatcher;
4440 }
4441
4442 Tab* tab = [_model currentTab];
4443 DCHECK(tab);
4444 auto* helper = FindTabHelper::FromWebState(tab.webState);
4445 DCHECK(!helper->IsFindUIActive());
4446 helper->SetFindUIActive(true);
4447 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4448}
4449
4450- (void)closeFindInPage {
4451 __weak BrowserViewController* weakSelf = self;
4452 Tab* currentTab = [_model currentTab];
4453 if (currentTab) {
4454 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4455 [weakSelf updateFindBar:NO shouldFocus:NO];
4456 });
4457 }
4458}
4459
4460- (void)searchFindInPage {
4461 DCHECK([_model currentTab]);
4462 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4463 __weak BrowserViewController* weakSelf = self;
4464 helper->StartFinding(
4465 [_findBarController searchTerm], ^(FindInPageModel* model) {
4466 BrowserViewController* strongSelf = weakSelf;
4467 if (!strongSelf) {
4468 return;
4469 }
4470 [strongSelf->_findBarController updateResultsCount:model];
4471 });
4472
4473 if (!_isOffTheRecord)
4474 helper->PersistSearchTerm();
4475}
4476
4477- (void)findNextStringInPage {
4478 Tab* currentTab = [_model currentTab];
4479 DCHECK(currentTab);
4480 // TODO(crbug.com/603524): Reshow find bar if necessary.
4481 FindTabHelper::FromWebState(currentTab.webState)
4482 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4483 [_findBarController updateResultsCount:model];
4484 });
4485}
4486
4487- (void)findPreviousStringInPage {
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::REVERSE, ^(FindInPageModel* model) {
4493 [_findBarController updateResultsCount:model];
4494 });
4495}
4496
edchinf84b2502017-08-31 21:30:454497- (void)showHelpPage {
4498 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4499 [self webPageOrderedOpen:helpUrl
4500 referrer:web::Referrer()
4501 inBackground:NO
4502 appendTo:kCurrentTab];
4503}
4504
edchinb59b5602017-09-01 15:00:204505- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104506 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204507 [self showAllBookmarks];
4508 } else {
4509 [self initializeBookmarkInteractionController];
4510 [_bookmarkInteractionController presentBookmarks];
4511 }
4512}
4513
edchin8ee0807d2017-09-01 23:52:474514- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104515 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474516 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4517 } else {
4518 if (!self.recentTabsCoordinator) {
4519 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4520 initWithBaseViewController:self];
4521 self.recentTabsCoordinator.loader = self;
4522 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4523 self.recentTabsCoordinator.browserState = _browserState;
4524 }
4525 [self.recentTabsCoordinator start];
4526 }
4527}
4528
Mark Cogan6de7e9a2017-09-06 12:57:214529- (void)requestDesktopSite {
4530 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4531}
4532
4533- (void)requestMobileSite {
4534 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4535}
4536
sdefresnee65fd872016-12-19 13:38:134537#pragma mark - Command Handling
4538
sdefresnee65fd872016-12-19 13:38:134539- (void)closeCurrentTab {
4540 Tab* currentTab = [_model currentTab];
4541 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4542 if (tabIndex == NSNotFound)
4543 return;
4544
jif7fed8122017-02-08 13:15:254545 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4546 // iPad.
sdefresnee65fd872016-12-19 13:38:134547 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4548 exitingPage.image =
4549 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4550
4551 // Close the actual tab, and add its image as a subview.
4552 [_model closeTabAtIndex:tabIndex];
4553
4554 // Do not animate close in iPad.
4555 if (!IsIPadIdiom()) {
4556 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524557 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134558 exitingPage, 0, YES, IsPortrait(), ^{
4559 [exitingPage removeFromSuperview];
4560 });
4561 }
4562}
4563
Elodie Banelaa5ab432017-09-28 14:42:014564- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4565 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434566 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134567 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4568 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014569 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464570 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014571 }
sdefresnee65fd872016-12-19 13:38:134572 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274573 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474574 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134575 if (_voiceSearchController)
4576 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364577
4578 Tab* currentTab = [_model currentTab];
4579 [currentTab dismissModals];
4580
rohitrao005a6432017-03-16 20:52:424581 if (currentTab) {
4582 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4583 if (findHelper) {
4584 findHelper->StopFinding(^{
4585 [self updateFindBar:NO shouldFocus:NO];
4586 });
4587 }
4588 }
rohitraob2bf3cb2017-02-10 14:10:364589
sdefresnee65fd872016-12-19 13:38:134590 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134591 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274592 _printController = nil;
sczsf1620e52017-10-02 22:54:464593 [_toolbarCoordinator dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134594 [_contextMenuCoordinator stop];
4595 [self dismissRateThisAppDialog];
4596
4597 if (self.presentedViewController) {
4598 // Dismisses any other modal controllers that may be present, e.g. Recent
4599 // Tabs.
4600 // Note that currently, some controllers like the bookmark ones were already
4601 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4602 // but are still reported as the presentedViewController. The result is that
4603 // this will call -dismissViewControllerAnimated:completion: a second time
4604 // on it. It is not per se an issue, as it is a no-op. The problem is that
4605 // in such a case, the completion block is not called.
4606 // To ensure the completion is called, nil is passed here, and the
4607 // completion is called below.
4608 [self dismissViewControllerAnimated:NO completion:nil];
4609 // Dismissed controllers will be so after a delay. Queue the completion
4610 // callback after that.
4611 if (completion) {
4612 dispatch_after(
4613 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4614 dispatch_get_main_queue(), ^{
4615 completion();
4616 });
4617 }
4618 } else if (completion) {
4619 // If no view controllers are presented, we should be ok with dispatching
4620 // the completion block directly.
4621 dispatch_async(dispatch_get_main_queue(), completion);
4622 }
4623}
4624
sdefresnee65fd872016-12-19 13:38:134625#pragma mark - Find Bar
4626
4627- (void)hideFindBarWithAnimation:(BOOL)animate {
4628 [_findBarController hideFindBarView:animate];
4629}
4630
4631- (void)showFindBarWithAnimation:(BOOL)animate
4632 selectText:(BOOL)selectText
4633 shouldFocus:(BOOL)shouldFocus {
4634 DCHECK(_findBarController);
4635 Tab* tab = [_model currentTab];
4636 DCHECK(tab);
4637 CRWWebController* webController = tab.webController;
4638
4639 CGRect referenceFrame = CGRectZero;
4640 if (IsIPadIdiom()) {
4641 referenceFrame = webController.visibleFrame;
4642 referenceFrame.origin.y -= kIPadFindBarOverlap;
4643 } else {
4644 referenceFrame = _contentArea.frame;
4645 }
4646
sczsf1620e52017-10-02 22:54:464647 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134648 [_findBarController addFindBarView:animate
4649 intoView:self.view
4650 withFrame:referenceFrame
4651 alignWithFrame:omniboxFrame
4652 selectText:selectText];
4653 [self updateFindBar:YES shouldFocus:shouldFocus];
4654}
4655
sdefresnee65fd872016-12-19 13:38:134656- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324657 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4658 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4659 // in a crash.
4660 if (![_model currentTab]) {
4661 return;
4662 }
rohitrao005a6432017-03-16 20:52:424663 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4664 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134665 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424666 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134667 }
4668
4669 [self setFramesForHeaders:[self headerViews]
4670 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424671 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134672 initialUpdate:initialUpdate
4673 focusTextfield:shouldFocus];
4674 } else {
4675 [self hideFindBarWithAnimation:YES];
4676 }
4677}
4678
4679- (void)showAllBookmarks {
4680 DCHECK(self.visible || self.dismissingModal);
4681 GURL URL(kChromeUIBookmarksURL);
4682 Tab* tab = [_model currentTab];
4683 web::NavigationManager::WebLoadParams params(URL);
4684 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234685 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134686}
4687
Gauthier Ambardf520c022017-08-29 07:42:234688- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134689 DCHECK(self.visible || self.dismissingModal);
4690 GURL url(kChromeUINewTabURL);
4691 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4692 if (fragment != "") {
4693 GURL::Replacements replacement;
4694 replacement.SetRefStr(fragment);
4695 url = url.ReplaceComponents(replacement);
4696 }
4697 Tab* tab = [_model currentTab];
4698 web::NavigationManager::WebLoadParams params(url);
4699 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234700 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134701}
4702
sdefresnee65fd872016-12-19 13:38:134703- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274704 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134705 base::RecordAction(base::UserMetricsAction(
4706 "IOSRateThisAppDialogDismissedProgramatically"));
4707 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274708 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134709 }
4710}
4711
Jean-François Geyelin5d2e184c2017-07-28 19:48:004712- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4713 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134714 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124715 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134716 _startVoiceSearchAfterNewTabAnimation = YES;
4717 return;
4718 }
4719
4720 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4721 // dismiss the keyboard.
4722 [self closeFindInPage];
4723 [[_model currentTab].webController dismissKeyboard];
4724
4725 // Ensure that voice search objects are created.
4726 [self ensureVoiceSearchControllerCreated];
4727 [self ensureVoiceSearchBarCreated];
4728
4729 // Present voice search.
4730 [_voiceSearchBar prepareToPresentVoiceSearch];
4731 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:464732 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:134733}
4734
4735#pragma mark - ToolbarOwner
4736
4737- (ToolbarController*)relinquishedToolbarController {
4738 if (_isToolbarControllerRelinquished)
4739 return nil;
4740
4741 ToolbarController* relinquishedToolbarController = nil;
sczsf1620e52017-10-02 22:54:464742 if ([_toolbarCoordinator view].hidden) {
sdefresnee65fd872016-12-19 13:38:134743 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:194744 if (currentTab.webState &&
4745 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
sdefresnee65fd872016-12-19 13:38:134746 // Use the native content controller's toolbar when the BVC's is hidden.
4747 id nativeController = [self nativeControllerForTab:currentTab];
4748 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4749 relinquishedToolbarController =
4750 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274751 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134752 }
4753 }
4754 } else {
sczsf1620e52017-10-02 22:54:464755 relinquishedToolbarController = _toolbarCoordinator.webToolbarController;
sdefresnee65fd872016-12-19 13:38:134756 }
4757 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4758 return relinquishedToolbarController;
4759}
4760
4761- (void)reparentToolbarController {
4762 if (_isToolbarControllerRelinquished) {
sczsf1620e52017-10-02 22:54:464763 if ([[_toolbarCoordinator view] isDescendantOfView:self.view]) {
sdefresnee65fd872016-12-19 13:38:134764 // A native content controller's toolbar has been relinquished.
4765 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274766 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134767 } else if ([_findBarController isFindInPageShown]) {
sczsf1620e52017-10-02 22:54:464768 [self.view insertSubview:[_toolbarCoordinator view]
sdefresnee65fd872016-12-19 13:38:134769 belowSubview:[_findBarController view]];
Jean-François Geyelined4cde72017-10-11 11:34:504770 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
4771 [self addConstraintsToToolbar];
4772 }
sdefresnee65fd872016-12-19 13:38:134773 } else {
sczsf1620e52017-10-02 22:54:464774 [self.view addSubview:[_toolbarCoordinator view]];
Jean-François Geyelined4cde72017-10-11 11:34:504775 if (base::FeatureList::IsEnabled(kSafeAreaCompatibleToolbar)) {
4776 [self addConstraintsToToolbar];
4777 }
sdefresnee65fd872016-12-19 13:38:134778 }
sdefresnee65fd872016-12-19 13:38:134779 _isToolbarControllerRelinquished = NO;
4780 }
4781}
4782
4783#pragma mark - TabModelObserver methods
4784
4785// Observer method, tab inserted.
4786- (void)tabModel:(TabModel*)model
4787 didInsertTab:(Tab*)tab
4788 atIndex:(NSUInteger)modelIndex
4789 inForeground:(BOOL)fg {
4790 DCHECK(tab);
4791 [self installDelegatesForTab:tab];
4792
4793 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194794 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134795 }
4796}
4797
4798// Observer method, active tab changed.
4799- (void)tabModel:(TabModel*)model
4800 didChangeActiveTab:(Tab*)newTab
4801 previousTab:(Tab*)previousTab
4802 atIndex:(NSUInteger)index {
4803 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4804 // Currently this observer method is always called with a non-nil |newTab|,
4805 // but that may change in the future. Remove this DCHECK when it does.
4806 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274807 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304808 DCHECK(newTab.webState);
4809 infobars::InfoBarManager* infoBarManager =
4810 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134811 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4812 }
4813 [self updateVoiceSearchBarVisibilityAnimated:NO];
4814
Mohamad Ahmadi7d09ec32017-07-11 22:32:194815 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134816
sczs6ae47ad2017-09-06 17:26:534817 // Update the Sad Tab coordinator webstate so it matches the current tab
4818 // webstate.
4819 _sadTabCoordinator.webState = newTab.webState;
4820
sdefresnee65fd872016-12-19 13:38:134821 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134822}
4823
4824// Observer method, tab changed.
4825- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4826 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4827 if (tab == [_model currentTab]) {
4828 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134829 }
4830}
4831
sdefresne49cf2862017-03-15 13:46:144832// Observer method, tab replaced.
4833- (void)tabModel:(TabModel*)model
4834 didReplaceTab:(Tab*)oldTab
4835 withTab:(Tab*)newTab
4836 atIndex:(NSUInteger)index {
4837 [self uninstallDelegatesForTab:oldTab];
4838 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264839
michaeldo79909fb2017-05-09 23:42:504840 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304841 infobars::InfoBarManager* infoBarManager = nullptr;
4842 if (newTab) {
4843 DCHECK(newTab.webState);
4844 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4845 }
michaeldo79909fb2017-05-09 23:42:504846 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4847 }
4848
kkhorimotofa0844cc2017-03-20 17:01:264849 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4850 if (self.active && model.currentTab == newTab)
4851 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464852
4853 if (newTab)
4854 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144855}
4856
sdefresnee65fd872016-12-19 13:38:134857// A tab has been removed, remove its views from display if necessary.
4858- (void)tabModel:(TabModel*)model
4859 didRemoveTab:(Tab*)tab
4860 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144861 [self uninstallDelegatesForTab:tab];
4862
kkhorimoto496fdd72017-06-12 19:56:314863 // Cancel dialogs for |tab|'s WebState.
4864 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4865
sdefresnee65fd872016-12-19 13:38:134866 // Ignore changes while the tab stack view is visible (or while suspended).
4867 // The display will be refreshed when this view becomes active again.
4868 if (!self.visible || !model.webUsageEnabled)
4869 return;
4870
4871 // Remove the find bar for now.
4872 [self hideFindBarWithAnimation:NO];
4873}
4874
4875- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4876 if (tab == [model currentTab]) {
4877 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464878 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134879 }
4880
Mohamad Ahmadi7d09ec32017-07-11 22:32:194881 [_paymentRequestManager stopTrackingWebState:tab.webState];
4882
sdefresnee65fd872016-12-19 13:38:134883 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4884 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194885 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134886 }
4887}
4888
4889// Called when the number of tabs changes. Update the toolbar accordingly.
4890- (void)tabModelDidChangeTabCount:(TabModel*)model {
4891 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464892 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134893}
4894
4895#pragma mark - Upgrade Detection
4896
4897- (void)showUpgrade:(UpgradeCenter*)center {
4898 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274899 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134900 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304901 DCHECK(tab.webState);
4902 infobars::InfoBarManager* infoBarManager =
4903 InfoBarManagerImpl::FromWebState(tab.webState);
4904 DCHECK(infoBarManager);
4905 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134906 }
4907}
4908
sdefresnee65fd872016-12-19 13:38:134909
4910#pragma mark - InfoBarControllerDelegate
4911
4912- (void)infoBarContainerStateChanged:(bool)isAnimating {
4913 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4914 DCHECK(infoBarContainerView);
4915 CGRect containerFrame = infoBarContainerView.frame;
4916 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4917 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4918 containerFrame.size.height = height;
4919 BOOL isViewVisible = self.visible;
4920 [UIView animateWithDuration:0.1
4921 animations:^{
4922 [infoBarContainerView setFrame:containerFrame];
4923 }
4924 completion:^(BOOL finished) {
4925 if (!isViewVisible)
4926 return;
4927 UIAccessibilityPostNotification(
4928 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4929 }];
4930}
4931
4932- (BOOL)shouldAutorotate {
4933 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4934 // Don't rotate if a voice search is being presented or dismissed. Once the
4935 // transition animations finish, only the Voice Search UIViewController's
4936 // |-shouldAutorotate| will be called.
4937 return NO;
4938 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4939 // Don't auto rotate if side swipe controller view says not to.
4940 return NO;
4941 } else {
4942 return [super shouldAutorotate];
4943 }
4944}
4945
4946// Always return yes, as this tap should work with various recognizers,
4947// including UITextTapRecognizer, UILongPressGestureRecognizer,
4948// UIScrollViewPanGestureRecognizer and others.
4949- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4950 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4951 (UIGestureRecognizer*)otherGestureRecognizer {
4952 return YES;
4953}
4954
4955// Tap gestures should only be recognized within |_contentArea|.
4956- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4957 CGPoint location = [gesture locationInView:self.view];
4958
4959 // Only allow touches on descendant views of |_contentArea|.
4960 UIView* hitView = [self.view hitTest:location withEvent:nil];
4961 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4962}
4963
4964#pragma mark - SideSwipeController Delegate Methods
4965
4966- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4967 DCHECK(!IsIPadIdiom());
4968 // Update frame incase orientation changed while |_contentArea| was out of
4969 // the view hierarchy.
4970 [_contentArea setFrame:[sideSwipeView frame]];
4971
4972 [self.view insertSubview:_contentArea atIndex:0];
4973 [self updateVoiceSearchBarVisibilityAnimated:NO];
4974 [self updateToolbar];
4975
4976 // Reset horizontal stack view.
4977 [sideSwipeView removeFromSuperview];
4978 [_sideSwipeController setInSwipe:NO];
4979 [_infoBarContainer->view() setHidden:NO];
4980}
4981
4982- (UIView*)contentView {
4983 return _contentArea;
4984}
4985
sdefresnee65fd872016-12-19 13:38:134986- (WebToolbarController*)toolbarController {
sczsf1620e52017-10-02 22:54:464987 return _toolbarCoordinator.webToolbarController;
sdefresnee65fd872016-12-19 13:38:134988}
4989
4990- (BOOL)preventSideSwipe {
sczsf1620e52017-10-02 22:54:464991 if ([_toolbarCoordinator toolsPopupController])
sdefresnee65fd872016-12-19 13:38:134992 return YES;
4993
4994 if (_voiceSearchController && _voiceSearchController->IsVisible())
4995 return YES;
4996
sdefresnee65fd872016-12-19 13:38:134997 if (!self.active)
4998 return YES;
4999
5000 return NO;
5001}
5002
5003- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5004 if (visible) {
5005 [self updateVoiceSearchBarVisibilityAnimated:NO];
5006 [self updateToolbar];
5007 [_infoBarContainer->view() setHidden:NO];
5008 } else {
5009 // Hide UI accessories such as find bar and first visit overlays
5010 // for welcome page.
5011 [self hideFindBarWithAnimation:NO];
5012 [_infoBarContainer->view() setHidden:YES];
5013 [_voiceSearchBar setHidden:YES];
5014 }
5015}
5016
5017- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5018 BOOL seenToolbar = NO;
5019 BOOL seenInfoBarContainer = NO;
5020 BOOL seenContentArea = NO;
5021 for (UIView* view in views.subviews) {
sczsf1620e52017-10-02 22:54:465022 if (view == [_toolbarCoordinator view])
sdefresnee65fd872016-12-19 13:38:135023 seenToolbar = YES;
5024 else if (view == _infoBarContainer->view())
5025 seenInfoBarContainer = YES;
5026 else if (view == _contentArea)
5027 seenContentArea = YES;
5028 if ((seenToolbar && !seenInfoBarContainer) ||
5029 (seenInfoBarContainer && !seenContentArea))
5030 return NO;
5031 }
5032 return YES;
5033}
5034
5035#pragma mark - PreloadControllerDelegate methods
5036
rohitraoeeb5293b2017-06-15 14:40:025037- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445038 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135039}
5040
rohitraoeeb5293b2017-06-15 14:40:025041- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5042 return [self hasControllerForURL:url];
5043}
5044
sdefresnee65fd872016-12-19 13:38:135045#pragma mark - BookmarkBridgeMethods
5046
5047// If an added or removed bookmark is the same as the current url, update the
5048// toolbar so the star highlight is kept in sync.
5049- (void)bookmarkNodeModified:(const BookmarkNode*)node {
Sylvain Defresnee7f2c8a2017-10-17 02:39:195050 if ([_model currentTab].webState &&
5051 node->url() == [_model currentTab].webState->GetLastCommittedURL()) {
sdefresnee65fd872016-12-19 13:38:135052 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255053 }
sdefresnee65fd872016-12-19 13:38:135054}
5055
5056// If all bookmarks are removed, update the toolbar so the star highlight is
5057// kept in sync.
5058- (void)allBookmarksRemoved {
5059 [self updateToolbar];
5060}
5061
sdefresnee65fd872016-12-19 13:38:135062- (void)showErrorAlertWithStringTitle:(NSString*)title
5063 message:(NSString*)message {
5064 // Dismiss current alert.
5065 [_alertCoordinator stop];
5066
stkhapuginc9eee7b2017-04-10 15:49:275067 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5068 message:message
5069 viewController:self];
sdefresnee65fd872016-12-19 13:38:135070 [_alertCoordinator start];
5071}
5072
edchineeb4d422017-10-02 17:39:365073- (void)showSnackbar:(NSString*)text {
5074 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5075 message.accessibilityLabel = text;
5076 message.duration = 2.0;
5077 message.category = kBrowserViewControllerSnackbarCategory;
5078 [self.dispatcher showSnackbarMessage:message];
5079}
5080
sdefresnee65fd872016-12-19 13:38:135081#pragma mark - Show Mail Composer methods
5082
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575083- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5084 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135085 if (![MFMailComposeViewController canSendMail]) {
5086 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575087 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135088 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575089 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135090 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5091 return;
5092 }
stkhapuginc9eee7b2017-04-10 15:49:275093 MFMailComposeViewController* mailViewController =
5094 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135095 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575096 [mailViewController setToRecipients:[context toRecipients]];
5097 [mailViewController setSubject:[context subject]];
5098 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135099
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575100 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135101 if (!textFile.empty()) {
5102 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5103 NSData* data = [NSData dataWithContentsOfFile:filename];
5104 if (data) {
5105 NSString* displayName =
5106 base::SysUTF8ToNSString(textFile.BaseName().value());
5107 [mailViewController addAttachmentData:data
5108 mimeType:@"text/plain"
5109 fileName:displayName];
5110 }
5111 }
5112
5113 [mailViewController setMailComposeDelegate:self];
5114 [self presentViewController:mailViewController animated:YES completion:nil];
5115}
5116
5117#pragma mark - MFMailComposeViewControllerDelegate methods
5118
5119- (void)mailComposeController:(MFMailComposeViewController*)controller
5120 didFinishWithResult:(MFMailComposeResult)result
5121 error:(NSError*)error {
5122 [self dismissViewControllerAnimated:YES completion:nil];
5123}
5124
5125#pragma mark - StoreKitLauncher methods
5126
5127- (void)productViewControllerDidFinish:
5128 (SKStoreProductViewController*)viewController {
5129 [self dismissViewControllerAnimated:YES completion:nil];
5130}
5131
5132- (void)openAppStore:(NSString*)appId {
5133 if (![appId length])
5134 return;
5135 NSDictionary* product =
5136 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275137 SKStoreProductViewController* storeViewController =
5138 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135139 [storeViewController setDelegate:self];
5140 [storeViewController loadProductWithParameters:product completionBlock:nil];
5141 [self presentViewController:storeViewController animated:YES completion:nil];
5142}
5143
5144#pragma mark - TabDialogDelegate methods
5145
sdefresnee65fd872016-12-19 13:38:135146- (void)cancelDialogForTab:(Tab*)tab {
5147 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5148}
5149
5150#pragma mark - FKFeedbackPromptDelegate methods
5151
5152- (void)userTappedRateApp:(UIView*)view {
5153 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275154 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135155}
5156
5157- (void)userTappedSendFeedback:(UIView*)view {
5158 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275159 _rateThisAppDialog = nil;
sczsb8f81c32017-08-29 15:45:495160 [self.dispatcher showReportAnIssue];
sdefresnee65fd872016-12-19 13:38:135161}
5162
5163- (void)userTappedDismiss:(UIView*)view {
5164 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275165 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135166}
5167
5168#pragma mark - VoiceSearchBarDelegate
5169
5170- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275171 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135172 [self ensureVoiceSearchControllerCreated];
5173 return _voiceSearchController->IsTextToSpeechEnabled() &&
5174 _voiceSearchController->IsTextToSpeechSupported();
5175}
5176
5177- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275178 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135179 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5180}
5181
5182#pragma mark - VoiceSearchPresenter
5183
5184- (UIView*)voiceSearchButton {
5185 return _voiceSearchButton;
5186}
5187
5188- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5189 return [self currentLogoAnimationControllerOwner];
5190}
5191
Rohit Rao01e0e002017-08-14 20:49:435192#pragma mark - ActivityService Providers
5193
5194- (void)presentActivityServiceViewController:(UIViewController*)controller {
5195 [self presentViewController:controller animated:YES completion:nil];
5196}
5197
5198- (void)activityServiceDidEndPresenting {
5199 self.presenting = NO;
5200 [self.dialogPresenter tryToPresent];
5201}
5202
Rohit Raocda0a992017-08-16 15:37:115203#pragma mark - QRScanner Requirements
5204
5205- (void)presentQRScannerViewController:(UIViewController*)controller {
5206 [self presentViewController:controller animated:YES completion:nil];
5207}
5208
5209- (void)dismissQRScannerViewController:(UIViewController*)controller
5210 completion:(void (^)(void))completion {
5211 DCHECK_EQ(controller, self.presentedViewController);
5212 [self dismissViewControllerAnimated:YES completion:completion];
5213}
5214
sczsdd860eba2017-08-10 01:55:385215#pragma mark - TabHistoryPresenter
5216
sczs0a726d22017-08-21 22:40:135217- (UIView*)viewForTabHistoryPresentation {
5218 return self.view;
5219}
5220
sczsdd860eba2017-08-10 01:55:385221- (void)prepareForTabHistoryPresentation {
5222 DCHECK(self.visible || self.dismissingModal);
5223 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465224 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385225}
5226
Mike Doughertya1ec26402017-08-23 19:46:315227#pragma mark - CaptivePortalDetectorTabHelperDelegate
5228
5229- (void)captivePortalBlockingPage:(IOSCaptivePortalBlockingPage*)blockingPage
5230 connectWithLandingURL:(GURL)landingURL {
5231 _captivePortalLoginCoordinator = [[CaptivePortalLoginCoordinator alloc]
5232 initWithBaseViewController:self
5233 landingURL:landingURL];
5234 [_captivePortalLoginCoordinator start];
5235}
5236
Gregory Chatzinoffdf93d692017-09-09 01:32:275237#pragma mark - PageInfoPresentation
5238
Gregory Chatzinoffb6a01f72017-09-20 20:06:395239- (void)presentPageInfoView:(UIView*)pageInfoView {
5240 [pageInfoView setFrame:self.view.bounds];
5241 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275242}
5243
5244- (void)prepareForPageInfoPresentation {
5245 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465246 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275247}
5248
Gregory Chatzinoffb6a01f72017-09-20 20:06:395249- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5250 return [self.view convertPoint:origin fromView:nil];
5251}
5252
Sylvain Defresnecacc3a52017-09-12 13:51:045253#pragma mark - WebStatePrinter
5254
5255- (void)printWebState:(web::WebState*)webState {
5256 if (webState == [_model currentTab].webState)
5257 [self printTab];
5258}
5259
Eugene But35ded552017-09-13 23:31:595260#pragma mark - RepostFormTabHelperDelegate
5261
5262- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
5263 presentRepostFromDialogAtPoint:(CGPoint)location
5264 completionHandler:(void (^)(BOOL))completion {
5265 _repostFormCoordinator = [[RepostFormCoordinator alloc]
5266 initWithBaseViewController:self
5267 dialogLocation:location
5268 webState:helper->web_state()
5269 completionHandler:completion];
5270 [_repostFormCoordinator start];
5271}
5272
5273- (void)repostFormTabHelperDismissRepostFormDialog:
5274 (RepostFormTabHelper*)helper {
5275 _repostFormCoordinator = nil;
5276}
5277
edchinf5150c682017-09-18 02:50:035278#pragma mark - TabStripPresentation
5279
5280- (BOOL)isTabStripFullyVisible {
5281 return ([self currentHeaderOffset] == 0.0f);
5282}
5283
5284- (void)showTabStripView:(UIView*)tabStripView {
5285 DCHECK([self isViewLoaded]);
5286 DCHECK(tabStripView);
5287 self.tabStripView = tabStripView;
5288 CGRect tabStripFrame = [self.tabStripView frame];
5289 tabStripFrame.origin = CGPointZero;
5290 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5291 // because the CGPointZero above will break reset the offset, but it's not
5292 // clear what removing that will do.
5293 tabStripFrame.origin.y = [self headerOffset];
5294 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5295 [self.tabStripView setFrame:tabStripFrame];
5296 [[self view] addSubview:tabStripView];
5297}
5298
edchincd32fdf2017-10-25 12:45:455299#pragma mark - ManageAccountsDelegate
5300
5301- (void)onManageAccounts {
5302 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5303 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5304 ->GetState());
5305 [self.dispatcher showAccountsSettings];
5306}
5307
5308- (void)onAddAccount {
5309 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5310 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5311 ->GetState());
5312 [self.dispatcher showAddAccount];
5313}
5314
5315- (void)onGoIncognito:(const GURL&)url {
5316 // The user taps on go incognito from the mobile U-turn webpage (the web page
5317 // that displays all users accounts available in the content area). As the
5318 // user chooses to go to incognito, the mobile U-turn page is no longer
5319 // neeeded. The current solution is to go back in history. This has the
5320 // advantage of keeping the current browsing session and give a good user
5321 // experience when the user comes back from incognito.
5322 [self.tabModel.currentTab goBack];
5323
5324 if (url.is_valid()) {
5325 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5326 initWithURL:url
5327 referrer:web::Referrer() // Strip referrer when switching modes.
5328 inIncognito:YES
5329 inBackground:NO
5330 appendTo:kLastTab];
5331 [self.dispatcher openURL:command];
5332 } else {
5333 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5334 }
5335}
5336
sdefresnee65fd872016-12-19 13:38:135337@end