blob: d9defdda0a2f062c3002dc4c413f354db7e4df46 [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"
54#include "components/strings/grit/components_strings.h"
55#include "components/toolbar/toolbar_model_impl.h"
56#include "ios/chrome/app/tests_hook.h"
57#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
58#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
59#include "ios/chrome/browser/chrome_url_constants.h"
60#include "ios/chrome/browser/chrome_url_util.h"
61#include "ios/chrome/browser/experimental_flags.h"
62#import "ios/chrome/browser/favicon/favicon_loader.h"
63#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2364#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
65#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1366#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
67#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3668#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1369#include "ios/chrome/browser/first_run/first_run.h"
70#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
71#include "ios/chrome/browser/infobars/infobar_container_ios.h"
72#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3073#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
sdefresnee65fd872016-12-19 13:38:1374#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
75#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1376#import "ios/chrome/browser/open_url_util.h"
77#import "ios/chrome/browser/passwords/password_controller.h"
sdefresnee65fd872016-12-19 13:38:1378#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5479#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
80#import "ios/chrome/browser/prerender/prerender_service.h"
81#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2482#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1383#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
84#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
85#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0586#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1387#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
88#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
89#import "ios/chrome/browser/snapshots/snapshot_cache.h"
90#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
91#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Mike Doughertya1ec26402017-08-23 19:46:3192#import "ios/chrome/browser/ssl/ios_captive_portal_blocking_page_delegate.h"
pkld6e73e52017-03-08 15:56:5193#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:2894#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1395#import "ios/chrome/browser/tabs/tab.h"
96#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:1997#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:1398#import "ios/chrome/browser/tabs/tab_model.h"
99#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16100#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13101#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Rohit Rao01e0e002017-08-14 20:49:43102#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
103#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
104#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_snackbar.h"
sdefresnee65fd872016-12-19 13:38:13105#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59106#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13107#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
108#import "ios/chrome/browser/ui/background_generator.h"
109#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
110#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13111#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38112#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
Mike Doughertya1ec26402017-08-23 19:46:31113#import "ios/chrome/browser/ui/captive_portal/captive_portal_login_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13114#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30115#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24116#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54117#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38118#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13119#import "ios/chrome/browser/ui/commands/open_url_command.h"
120#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00121#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23122#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13123#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13124#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
125#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
126#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
127#import "ios/chrome/browser/ui/external_file_controller.h"
Eugene Butf2d7fee2017-09-16 01:27:55128#import "ios/chrome/browser/ui/external_file_remover.h"
sdefresnee65fd872016-12-19 13:38:13129#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
130#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
131#import "ios/chrome/browser/ui/fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38132#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13133#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13134#import "ios/chrome/browser/ui/key_commands_provider.h"
Gauthier Ambard33720dd2017-09-06 15:36:51135#import "ios/chrome/browser/ui/ntp/incognito_view_controller.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10136#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13137#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42138#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13139#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27140#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
141#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13142#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37143#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13144#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11145#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
146#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03148#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13149#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13150#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53151#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39152#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13153#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
154#import "ios/chrome/browser/ui/stack_view/card_view.h"
155#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
156#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13157#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03158#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
159#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
160#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13161#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
162#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
163#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sczsbbad1632017-07-29 03:48:00164#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13165#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
166#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
167#include "ios/chrome/browser/ui/ui_util.h"
168#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28169#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13170#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
171#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51172#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13173#import "ios/chrome/browser/web/error_page_content.h"
174#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04175#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05176#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59177#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53178#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04179#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05180#import "ios/chrome/browser/web_state_list/web_state_list.h"
181#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57182#import "ios/chrome/browser/webui/net_export_tab_helper.h"
183#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
184#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13185#include "ios/chrome/grit/ios_chromium_strings.h"
186#include "ios/chrome/grit/ios_strings.h"
187#import "ios/net/request_tracker.h"
188#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
189#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
190#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
191#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
192#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
193#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
194#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
195#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
sdefresnee65fd872016-12-19 13:38:13196#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13197#include "ios/web/public/navigation_item.h"
198#import "ios/web/public/navigation_manager.h"
199#include "ios/web/public/referrer_util.h"
200#include "ios/web/public/ssl_status.h"
201#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39202#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13203#include "ios/web/public/web_client.h"
204#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13205#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29206#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13207#include "ios/web/public/web_state/web_state.h"
208#import "ios/web/public/web_state/web_state_delegate_bridge.h"
209#include "ios/web/public/web_thread.h"
210#import "ios/web/web_state/ui/crw_web_controller.h"
211#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17212#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13213#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
214#include "net/ssl/ssl_info.h"
215#include "net/url_request/url_request_context_getter.h"
216#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
217#include "ui/base/l10n/l10n_util.h"
218#include "ui/base/l10n/l10n_util_mac.h"
219#include "ui/base/page_transition_types.h"
220#include "url/gurl.h"
221
stkhapuginf58b10d02017-04-10 13:36:17222#if !defined(__has_feature) || !__has_feature(objc_arc)
223#error "This file requires ARC support."
224#endif
225
sdefresnee65fd872016-12-19 13:38:13226using base::UserMetricsAction;
227using bookmarks::BookmarkNode;
228
229class BrowserBookmarkModelBridge;
230class InfoBarContainerDelegateIOS;
231
sdefresnee65fd872016-12-19 13:38:13232NSString* const kLocationBarBecomesFirstResponderNotification =
233 @"kLocationBarBecomesFirstResponderNotification";
234NSString* const kLocationBarResignsFirstResponderNotification =
235 @"kLocationBarResignsFirstResponderNotification";
sdefresnee65fd872016-12-19 13:38:13236
237namespace {
238
239typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
240 // Note: these values must match the ContextMenuOption enum in histograms.xml.
241 ACTION_OPEN_IN_NEW_TAB = 0,
242 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
243 ACTION_COPY_LINK_ADDRESS = 2,
244 ACTION_SAVE_IMAGE = 6,
245 ACTION_OPEN_IMAGE = 7,
246 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
247 ACTION_SEARCH_BY_IMAGE = 11,
248 ACTION_OPEN_JAVASCRIPT = 21,
249 ACTION_READ_LATER = 22,
250 NUM_ACTIONS = 23,
251};
252
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28253void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13254 if (is_image) {
255 if (is_link) {
256 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
257 NUM_ACTIONS);
258 } else {
259 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
260 NUM_ACTIONS);
261 }
262 } else {
263 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
264 NUM_ACTIONS);
265 }
266}
267
edchinf5150c682017-09-18 02:50:03268// Returns the status bar background color.
269UIColor* StatusBarBackgroundColor() {
270 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
271}
272
273// Duration of the toolbar animation.
274const NSTimeInterval kFullScreenControllerToolbarAnimationDuration = 0.3;
275
sdefresnee65fd872016-12-19 13:38:13276const CGFloat kVoiceSearchBarHeight = 59.0;
277
278// Dimensions to use when downsizing an image for search-by-image.
279const CGFloat kSearchByImageMaxImageArea = 90000.0;
280const CGFloat kSearchByImageMaxImageWidth = 600.0;
281const CGFloat kSearchByImageMaxImageHeight = 400.0;
282
Eugene Butf2d7fee2017-09-16 01:27:55283// The delay, in seconds, after startup before cleaning up the files received
284// from other applications that are not bookmarked nor referenced by an open or
285// recently closed tab.
286const int kExternalFilesCleanupDelaySeconds = 60;
287
sdefresnee65fd872016-12-19 13:38:13288enum HeaderBehaviour {
289 // The header moves completely out of the screen.
290 Hideable = 0,
291 // This header stays on screen and doesn't overlap with the content.
292 Visible,
293 // This header stay on screen and covers part of the content.
294 Overlap
295};
296
sdefresnee65fd872016-12-19 13:38:13297const CGFloat kIPadFindBarOverlap = 11;
298
299bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24300 // Most URLs are allowed in incognito; the following is an exception.
301 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13302}
303
rohitrao005a6432017-03-16 20:52:42304} // namespace
sdefresnee65fd872016-12-19 13:38:13305
stkhapugin952ecef2017-04-11 12:11:45306#pragma mark - HeaderDefinition helper
307
308@interface HeaderDefinition : NSObject
309
310// The header view.
311@property(nonatomic, strong) UIView* view;
312// How to place the view, and its behaviour when the headers move.
313@property(nonatomic, assign) HeaderBehaviour behaviour;
314// Reduces the height of a header to adjust for shadows.
315@property(nonatomic, assign) CGFloat heightAdjustement;
316// Nudges that particular header up by this number of points.
317@property(nonatomic, assign) CGFloat inset;
318
319- (instancetype)initWithView:(UIView*)view
320 headerBehaviour:(HeaderBehaviour)behaviour
321 heightAdjustment:(CGFloat)heightAdjustment
322 inset:(CGFloat)inset;
323
324+ (instancetype)definitionWithView:(UIView*)view
325 headerBehaviour:(HeaderBehaviour)behaviour
326 heightAdjustment:(CGFloat)heightAdjustment
327 inset:(CGFloat)inset;
328
329@end
330
331@implementation HeaderDefinition
332@synthesize view = _view;
333@synthesize behaviour = _behaviour;
334@synthesize heightAdjustement = _heightAdjustement;
335@synthesize inset = _inset;
336
337+ (instancetype)definitionWithView:(UIView*)view
338 headerBehaviour:(HeaderBehaviour)behaviour
339 heightAdjustment:(CGFloat)heightAdjustment
340 inset:(CGFloat)inset {
341 return [[self alloc] initWithView:view
342 headerBehaviour:behaviour
343 heightAdjustment:heightAdjustment
344 inset:inset];
345}
346
347- (instancetype)initWithView:(UIView*)view
348 headerBehaviour:(HeaderBehaviour)behaviour
349 heightAdjustment:(CGFloat)heightAdjustment
350 inset:(CGFloat)inset {
351 self = [super init];
352 if (self) {
353 _view = view;
354 _behaviour = behaviour;
355 _heightAdjustement = heightAdjustment;
356 _inset = inset;
357 }
358 return self;
359}
360
361@end
362
363#pragma mark - BVC
364
Rohit Rao01e0e002017-08-14 20:49:43365@interface BrowserViewController ()<ActivityServicePresentation,
366 ActivityServiceSnackbar,
367 AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13368 CRWNativeContentProvider,
369 CRWWebStateDelegate,
370 DialogPresenterDelegate,
371 FullScreenControllerDelegate,
Gauthier Ambard8160349c2017-09-06 14:43:24372 IncognitoViewControllerDelegate,
Mike Doughertya1ec26402017-08-23 19:46:31373 IOSCaptivePortalBlockingPageDelegate,
sdefresnee65fd872016-12-19 13:38:13374 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57375 NetExportTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13376 MFMailComposeViewControllerDelegate,
377 NewTabPageControllerObserver,
378 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27379 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13380 PassKitDialogProvider,
381 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11382 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59383 RepostFormTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13384 SKStoreProductViewControllerDelegate,
385 SnapshotOverlayProvider,
386 StoreKitLauncher,
387 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19388 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38389 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13390 TabModelObserver,
391 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03392 TabStripPresentation,
sdefresnee65fd872016-12-19 13:38:13393 UIGestureRecognizerDelegate,
394 UpgradeCenterClientProtocol,
395 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04396 VoiceSearchBarOwner,
397 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13398 // The dependency factory passed on initialization. Used to vend objects used
399 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27400 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13401
402 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27403 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13404
405 // Facade objects used by |_toolbarController|.
406 // Must outlive |_toolbarController|.
407 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
408 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
409
sdefresnee65fd872016-12-19 13:38:13410 // The WebToolbarController used to display the omnibox.
stkhapuginc9eee7b2017-04-10 15:49:27411 WebToolbarController* _toolbarController;
sdefresnee65fd872016-12-19 13:38:13412
413 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27414 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13415
Mike Doughertya1ec26402017-08-23 19:46:31416 // Handles displaying the captive portal login page.
417 CaptivePortalLoginCoordinator* _captivePortalLoginCoordinator;
418
sdefresnee65fd872016-12-19 13:38:13419 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27420 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13421
422 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27423 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13424
425 // Handles presentation of JavaScript dialogs.
426 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
427
justincohen75011c32017-04-28 16:31:39428 // Handles command dispatching.
429 CommandDispatcher* _dispatcher;
430
sdefresnee65fd872016-12-19 13:38:13431 // Keyboard commands provider. It offloads most of the keyboard commands
432 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27433 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13434
435 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
436 // to |-reparentToolbarController| will reset it to NO.
437 BOOL _isToolbarControllerRelinquished;
438
439 // The controller that owns the currently relinquished toolbar controller.
440 // The reference is weak because it's possible for the toolbar owner to be
441 // deallocated mid-animation due to memory pressure or a tab being closed
442 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27443 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13444
sdefresnee65fd872016-12-19 13:38:13445 // Used to inject Javascript implementing the PaymentRequest API and to
446 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27447 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13448
sdefresnee65fd872016-12-19 13:38:13449 // Used to display the Voice Search UI. Nil if not visible.
450 scoped_refptr<VoiceSearchController> _voiceSearchController;
451
gambard6299cc1d2017-02-21 13:06:03452 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27453 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03454
sdefresnee65fd872016-12-19 13:38:13455 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27456 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13457
sdefresnee65fd872016-12-19 13:38:13458 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27459 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13460
461 // Records the set of domains for which full screen alert has already been
462 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27463 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13464
465 // Adapter to let BVC be the delegate for WebState.
466 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
467
468 // YES if new tab is animating in.
469 BOOL _inNewTabAnimation;
470
471 // YES if Voice Search should be started when the new tab animation is
472 // finished.
473 BOOL _startVoiceSearchAfterNewTabAnimation;
474
475 // YES if the user interacts with the location bar.
476 BOOL _locationBarHasFocus;
477 // YES if a load was cancelled due to typing in the location bar.
478 BOOL _locationBarEditCancelledLoad;
479 // YES if waiting for a foreground tab due to expectNewForegroundTab.
480 BOOL _expectingForegroundTab;
481
Sylvain Defresne41170aa2017-06-15 10:25:20482 // Whether or not -shutdown has been called.
483 BOOL _isShutdown;
484
sdefresnee65fd872016-12-19 13:38:13485 // The ChromeBrowserState associated with this BVC.
486 ios::ChromeBrowserState* _browserState; // weak
487
488 // Whether or not Incognito* is enabled.
489 BOOL _isOffTheRecord;
490
491 // The last point within |_contentArea| that's received a touch.
492 CGPoint _lastTapPoint;
493
494 // The time at which |_lastTapPoint| was most recently set.
495 CFTimeInterval _lastTapTime;
496
497 // A single infobar container handles all infobars in all tabs. It keeps
498 // track of infobars for current tab (accessed via infobar helper of
499 // the current tab).
500 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
501
502 // Bridge class to deliver container change notifications to BVC.
503 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
504
505 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37506 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27507 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13508
509 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11510 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13511
sdefresnee65fd872016-12-19 13:38:13512 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27513 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13514
515 // Bridge to register for bookmark changes.
516 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
517
518 // Cached pointer to the bookmarks model.
519 bookmarks::BookmarkModel* _bookmarkModel; // weak
520
521 // The controller that shows the bookmarking UI after the user taps the star
522 // button.
stkhapuginc9eee7b2017-04-10 15:49:27523 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13524
Eugene Butf2d7fee2017-09-16 01:27:55525 // Used to remove unreferenced external files.
526 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
527
sdefresnee65fd872016-12-19 13:38:13528 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27529 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13530
Eugene But56efc322017-08-11 14:03:44531 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28532 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13533
534 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27535 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13536
Jean-François Geyelin3d47c212017-08-03 09:24:09537 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27538 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13539
Rohit Rao01e0e002017-08-14 20:49:43540 // Coordinator for the share menu (Activity Services).
541 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
542
sdefresnee65fd872016-12-19 13:38:13543 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27544 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38545
Rohit Raocda0a992017-08-16 15:37:11546 // Coordinator for the QR scanner.
547 QRScannerLegacyCoordinator* _qrScannerCoordinator;
548
sczsdd860eba2017-08-10 01:55:38549 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13550 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53551
552 // Coordinator for displaying Sad Tab.
553 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27554
555 // Coordinator for Page Info UI.
556 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59557
558 // Coordinator for displaying Repost Form dialog.
559 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22560
561 // Fake status bar view used to blend the toolbar into the status bar.
562 UIView* _fakeStatusBarView;
sdefresnee65fd872016-12-19 13:38:13563}
564
565// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17566@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13567// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17568@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13569// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17570@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13571// Whether the current tab can enable the request desktop menu item.
572@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
573// Whether the sharing menu should be enabled.
574@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
575// Helper method to check web controller canShowFindBar method.
576@property(nonatomic, assign, readonly) BOOL canShowFindBar;
577// Whether the controller's view is currently available.
578// YES from viewWillAppear to viewWillDisappear.
579@property(nonatomic, assign, getter=isVisible) BOOL visible;
580// Whether the controller's view is currently visible.
581// YES from viewDidAppear to viewWillDisappear.
582@property(nonatomic, assign) BOOL viewVisible;
583// Whether the controller is currently dismissing a presented view controller.
584@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
585// Returns YES if the toolbar has not been scrolled out by fullscreen.
586@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
587 BOOL toolbarOnScreen;
588// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12589@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13590// Whether BVC prefers to hide the status bar. This value is used to determine
591// the response from the |prefersStatusBarHidden| method.
592@property(nonatomic, assign) BOOL hideStatusBar;
593// Whether the VoiceSearchBar should be displayed.
594@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
595// Coordinator for displaying a modal overlay with activity indicator to prevent
596// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17597@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13598 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39599// A block to be run when the |tabWasAdded:| method completes the animation
600// for the presentation of a new tab. Can be used to record performance metrics.
601@property(nonatomic, strong, nullable)
602 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42603// Coordinator for Recent Tabs.
604@property(nonatomic, strong)
605 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03606// Coordinator for tablet tab strip.
607@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
608// A weak reference to the view of the tab strip on tablet.
609@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13610
liaoyukeea9f3ee62017-03-07 22:05:39611// The user agent type used to load the currently visible page. User agent type
612// is NONE if there is no visible page or visible page is a native page.
613@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
614
stkhapugin952ecef2017-04-11 12:11:45615// Returns the header views, all the chrome on top of the page, including the
616// ones that cannot be scrolled off screen by full screen.
617@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
618
Cooper Knaakd0a974cd2017-08-10 18:05:47619// Used to display the new tab tip in-product help promotion bubble. |nil| if
620// the new tab tip bubble has not yet been presented. Once the bubble is
621// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38622@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47623 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38624
Helen Yang9175bd52017-08-12 00:28:40625// Used to display the new incognito tab tip in-product help promotion bubble.
626@property(nonatomic, strong)
627 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
628
sdefresnee65fd872016-12-19 13:38:13629// BVC initialization:
630// If the BVC is initialized with a valid browser state & tab model immediately,
631// the path is straightforward: functionality is enabled, and the UI is built
632// when -viewDidLoad is called.
633// If the BVC is initialized without a browser state or tab model, the tab model
634// and browser state may or may not be provided before -viewDidLoad is called.
635// In most cases, they will not, to improve startup performance.
636// In order to handle this, initialization of various aspects of BVC have been
637// broken out into the following functions, which have expectations (enforced
638// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
639
640// Registers for notifications.
641- (void)registerForNotifications;
642// Called when a tab is starting to load. If it's a link click or form
643// submission, the user is navigating away from any entries in the forward
644// history. Tell the toolbar so it can update the UI appropriately.
645// See the warning on [Tab webWillStartLoadingURL] about invocation of this
646// method sequence by malicious pages.
647- (void)pageLoadStarting:(NSNotification*)notify;
648// Called when a tab actually starts loading.
649- (void)pageLoadStarted:(NSNotification*)notify;
650// Called when a tab finishes loading. Update the Omnibox with the url and
651// stop any page load progess display.
652- (void)pageLoadComplete:(NSNotification*)notify;
653// Called when a tab is deselected in the model.
654// This notification also occurs when a tab is closed.
655- (void)tabDeselected:(NSNotification*)notify;
656// Animates sliding current tab and rotate-entering new tab while new tab loads
657// in background on the iPhone only.
658- (void)tabWasAdded:(NSNotification*)notify;
659
660// Updates non-view-related functionality with the given browser state and tab
661// model.
662// Does not matter whether or not the view has been loaded.
663- (void)updateWithTabModel:(TabModel*)model
664 browserState:(ios::ChromeBrowserState*)browserState;
665// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
666// the status bar to mimic this layout.
667- (void)installFakeStatusBar;
668// Builds the UI parts of tab strip and the toolbar. Does not matter whether
669// or not browser state and tab model are valid.
670- (void)buildToolbarAndTabStrip;
671// Updates view-related functionality with the given tab model and browser
672// state. The view must have been loaded. Uses |_browserState| and |_model|.
673- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22674// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13675- (void)setUpViewLayout;
sdefresnee65fd872016-12-19 13:38:13676// Makes |tab| the currently visible tab, displaying its view. Calls
677// -selectedTabChanged on the toolbar only if |newSelection| is YES.
678- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
679// Initializes the bookmark interaction controller if not already initialized.
680- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13681// Add all delegates to the provided |tab|.
682- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14683// Remove delegates from the provided |tab|.
684- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13685// Closes the current tab, with animation if applicable.
686- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13687// Show the bookmarks page.
688- (void)showAllBookmarks;
689// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23690- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13691// Dismisses the "rate this app" dialog.
692- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32693// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13694- (BOOL)isTabNativePage:(Tab*)tab;
695// Returns the view to use when animating a page in or out, positioning it to
696// fill the content area but not actually adding it to the view hierarchy.
697- (UIImageView*)pageOpenCloseAnimationView;
698// Returns the view to use when animating full screen NTP paper in, filling the
699// entire screen but not actually adding it to the view hierarchy.
700- (UIImageView*)pageFullScreenOpenCloseAnimationView;
701// Updates the toolbar display based on the current tab.
702- (void)updateToolbar;
703// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12704// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13705- (void)updateDialogPresenterActiveState;
706// Dismisses popups and modal dialogs that are displayed above the BVC upon size
707// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
708// is performed.
709// TODO(crbug.com/522721): Support size changes for all popups and modal
710// dialogs.
711- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47712
713// Returns a bubble associated with an in-product help promotion if
714// it is valid to show the promotion and |nil| otherwise. |feature| is the
715// base::Feature object associated with the given promotion. |direction| is the
716// direction the bubble's arrow is pointing. |alignment| is the alignment of the
717// arrow on the button. |text| is the text displayed by the bubble.
718- (BubbleViewControllerPresenter*)
719bubblePresenterForFeature:(const base::Feature&)feature
720 direction:(BubbleArrowDirection)direction
721 alignment:(BubbleAlignment)alignment
722 text:(NSString*)text;
723
Cooper Knaak120cee5e2017-08-10 20:57:00724// Waits to present a bubble associated with the new tab tip in-product help
725// promotion until the feature engagement tracker database is fully initialized.
726// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
727// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11728// |userEngaged|. Does not present the bubble if the feature engagement tracker
729// determines it is not valid to present it.
Cooper Knaak120cee5e2017-08-10 20:57:00730- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11731// Optionally presents a bubble associated with the new tab tip in-product help
732// promotion. If the feature engagement tracker determines it is valid to show
733// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
734// the bubble. If it is not valid to show the new tab tip,
735// |tabTipBubblePresenter| is set to |nil| and no bubble is shown.
Cooper Knaak120cee5e2017-08-10 20:57:00736- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40737// Waits to present a bubble associated with the new incognito tab tip
738// in-product help promotion until the feature engagement tracker database is
739// fully initialized.
740- (void)presentNewIncognitoTabTipBubbleOnInitialized;
741// Presents a bubble associated with the new incognito tab tip in-product help
742// promotion.
743- (void)presentNewIncognitoTabTipBubble;
Cooper Knaak120cee5e2017-08-10 20:57:00744
sdefresnee65fd872016-12-19 13:38:13745// Update find bar with model data. If |shouldFocus| is set to YES, the text
746// field will become first responder.
747- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13748// Hide find bar.
749- (void)hideFindBarWithAnimation:(BOOL)animate;
750// Shows find bar. If |selectText| is YES, all text inside the Find Bar
751// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
752// set to be first responder.
753- (void)showFindBarWithAnimation:(BOOL)animate
754 selectText:(BOOL)selectText
755 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36756
sdefresnee65fd872016-12-19 13:38:13757// The infobar state (typically height) has changed.
758- (void)infoBarContainerStateChanged:(bool)is_animating;
759// Adds a CardView on top of the contentArea either taking the size of the full
760// screen or just the size of the space under the header.
761// Returns the CardView that was added.
762- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
763// Called when either a tab finishes loading or when a tab with finished content
764// is added directly to the model via pre-rendering. The tab must be non-nil and
765// must be a member of the tab model controlled by this BrowserViewController.
766- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
767// Evaluates Javascript asynchronously using the current page context.
768- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13769// Shows a self-dismissing snackbar displaying |message|.
770- (void)showSnackbar:(NSString*)message;
771// Induces an intentional crash in the browser process.
772- (void)induceBrowserCrash;
773// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17774- (void)managePermissionAndSaveImage:(NSData*)data
775 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13776// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24777// saved as a temporary file on disk then saved in photos. Saving will happen
778// on a background sequence and the completion block will be invoked on that
779// sequence.
780- (void)saveImage:(NSData*)data
781 withFileExtension:(NSString*)fileExtension
782 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13783// Called when Chrome has been denied access to the photos or videos and the
784// user can change it.
785// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
786// settings. Dismiss previous alert if it has not been dismissed yet.
787- (void)displayImageErrorAlertWithSettingsOnMainQueue;
788// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
789// previous alert if it has not been dismissed yet.
790- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
791// Called when Chrome has been denied access to the photos or videos and the
792// user cannot change it.
793// Shows a privacy alert on the main queue, with errorContent as the message.
794// Dismisses previous alert if it has not been dismissed yet.
795- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
796// Called with the results of saving a picture in the photo album. If error is
797// nil the save succeeded.
798- (void)finishSavingImageWithError:(NSError*)error;
799// Provides a view that encompasses currently displayed infobar(s) or nil
800// if no infobar is presented.
801- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
802// Returns a vertical infobar offset relative to the tab content.
803- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
804// Provides a view that encompasses the voice search bar if it's displayed or
805// nil if the voice search bar isn't displayed.
806- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
807// Returns a vertical voice search bar offset relative to the tab content.
808- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
809// Lazily instantiates |_voiceSearchController|.
810- (void)ensureVoiceSearchControllerCreated;
811// Lazily instantiates |_voiceSearchBar| and adds it to the view.
812- (void)ensureVoiceSearchBarCreated;
813// Shows/hides the voice search bar.
814- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
815// The LogoAnimationControllerOwner to be used for the next logo transition
816// animation.
817- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13818// Returns the footer view if one exists (e.g. the voice search bar).
819- (UIView*)footerView;
820// Returns the height of the header view for the tab model's current tab.
821- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13822// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45823- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13824 atOffset:(CGFloat)headerOffset;
825// Returns the y coordinate for the footer's frame when animating the footer
826// in/out of fullscreen.
827- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
828// Called when the animation for setting the header view's offset is finished.
829// |completed| should indicate if the animation finished completely or was
830// interrupted. |offset| should indicate the header offset after the animation.
831// |dragged| should indicate if the header moved due to the user dragging.
832- (void)fullScreenController:(FullScreenController*)controller
833 headerAnimationCompleted:(BOOL)completed
834 offset:(CGFloat)offset;
835// Performs a search with the image at the given url. The referrer is used to
836// download the image.
837- (void)searchByImageAtURL:(const GURL&)url
838 referrer:(const web::Referrer)referrer;
839// Saves the image at the given URL on the system's album. The referrer is used
840// to download the image.
841- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
842
Mark Cogandfcdea72017-07-18 13:47:38843// Record the last tap point based on the |originPoint| (if any) passed in
844// |command|.
845- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13846// Get return the last stored |_lastTapPoint| if it's been set within the past
847// second.
848- (CGPoint)lastTapPoint;
849// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
850- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
851// Returns the native controller being used by |tab|'s web controller.
852- (id)nativeControllerForTab:(Tab*)tab;
853// Installs the BVC as overscroll actions controller of |nativeContent| if
854// needed. Sets the style of the overscroll actions toolbar.
855- (void)setOverScrollActionControllerToStaticNativeContent:
856 (StaticHtmlNativeContent*)nativeContent;
857// Whether the BVC should declare keyboard commands.
858- (BOOL)shouldRegisterKeyboardCommands;
859// Adds the given url to the reading list.
860- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
861@end
862
863class InfoBarContainerDelegateIOS
864 : public infobars::InfoBarContainer::Delegate {
865 public:
866 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
867 : controller_(controller) {}
868
869 ~InfoBarContainerDelegateIOS() override {}
870
871 private:
872 SkColor GetInfoBarSeparatorColor() const override {
873 NOTIMPLEMENTED();
874 return SK_ColorBLACK;
875 }
876
877 int ArrowTargetHeightForInfoBar(
878 size_t index,
879 const gfx::SlideAnimation& animation) const override {
880 return 0;
881 }
882
883 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
884 int arrow_target_height,
885 int bar_target_height,
886 int* arrow_height,
887 int* arrow_half_width,
888 int* bar_height) const override {
889 DCHECK_NE(-1, bar_target_height)
890 << "Infobars don't have a default height on iOS";
891 *arrow_height = 0;
892 *arrow_half_width = 0;
893 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
894 }
895
896 void InfoBarContainerStateChanged(bool is_animating) override {
897 [controller_ infoBarContainerStateChanged:is_animating];
898 }
899
900 bool DrawInfoBarArrows(int* x) const override { return false; }
901
stkhapuginf58b10d02017-04-10 13:36:17902 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13903};
904
905// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
906@interface BrowserViewController (BookmarkBridgeMethods)
907// If a bookmark matching the currentTab url is added or moved, update the
908// toolbar state so the star highlight is in sync.
909- (void)bookmarkNodeModified:(const BookmarkNode*)node;
910- (void)allBookmarksRemoved;
911@end
912
913// Handle notification that bookmarks has been removed changed so we can update
914// the bookmarked star icon.
915class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
916 public:
917 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
918 : owner_(owner) {}
919
920 ~BrowserBookmarkModelBridge() override {}
921
922 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
923 const BookmarkNode* parent,
924 int old_index,
925 const BookmarkNode* node,
926 const std::set<GURL>& removed_urls) override {
927 [owner_ bookmarkNodeModified:node];
928 }
929
930 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
931 bool ids_reassigned) override {}
932
933 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
934 const BookmarkNode* old_parent,
935 int old_index,
936 const BookmarkNode* new_parent,
937 int new_index) override {}
938
939 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
940 const BookmarkNode* parent,
941 int index) override {
942 [owner_ bookmarkNodeModified:parent->GetChild(index)];
943 }
944
945 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
946 const BookmarkNode* node) override {}
947
948 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
949 const BookmarkNode* node) override {}
950
951 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
952 const BookmarkNode* node) override {}
953
954 void BookmarkAllUserNodesRemoved(
955 bookmarks::BookmarkModel* model,
956 const std::set<GURL>& removed_urls) override {
957 [owner_ allBookmarksRemoved];
958 }
959
960 private:
stkhapuginf58b10d02017-04-10 13:36:17961 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13962};
963
964@implementation BrowserViewController
965
966@synthesize contentArea = _contentArea;
967@synthesize typingShield = _typingShield;
968@synthesize active = _active;
969@synthesize visible = _visible;
970@synthesize viewVisible = _viewVisible;
971@synthesize dismissingModal = _dismissingModal;
972@synthesize hideStatusBar = _hideStatusBar;
973@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
974@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39975@synthesize foregroundTabWasAddedCompletionBlock =
976 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40977@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
978@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42979@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03980@synthesize tabStripCoordinator = _tabStripCoordinator;
981@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:13982
983#pragma mark - Object lifecycle
984
Mark Cogan5e3da152017-07-11 15:57:30985- (instancetype)
986 initWithTabModel:(TabModel*)model
987 browserState:(ios::ChromeBrowserState*)browserState
988 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
989applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13990 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
991 if (self) {
992 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17993
stkhapuginc9eee7b2017-04-10 15:49:27994 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27995 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
996 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39997 _dispatcher = [[CommandDispatcher alloc] init];
998 [_dispatcher startDispatchingToTarget:self
999 forProtocol:@protocol(UrlLoader)];
1000 [_dispatcher startDispatchingToTarget:self
1001 forProtocol:@protocol(WebToolbarDelegate)];
1002 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241003 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301004 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1005 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321006 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1007 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1008 // dispatched to the endpoint as well. Since this is potentially
1009 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1010 DCHECK(!applicationCommandEndpoint ||
1011 [applicationCommandEndpoint
1012 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1013 [_dispatcher
1014 startDispatchingToTarget:applicationCommandEndpoint
1015 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391016
sdefresnee65fd872016-12-19 13:38:131017 _javaScriptDialogPresenter.reset(
1018 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1019 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1020 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151021 [[UpgradeCenter sharedInstance] registerClient:self
1022 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131023 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131024 if (model && browserState)
1025 [self updateWithTabModel:model browserState:browserState];
1026 if ([[NSUserDefaults standardUserDefaults]
1027 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271028 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131029 }
1030 }
1031 return self;
1032}
1033
1034- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1035 bundle:(NSBundle*)nibBundleOrNil {
1036 NOTREACHED();
1037 return nil;
1038}
1039
1040- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1041 NOTREACHED();
1042 return nil;
1043}
1044
1045- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201046 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131047}
1048
1049#pragma mark - Accessibility
1050
1051- (BOOL)accessibilityPerformEscape {
1052 [self dismissPopups];
1053 return YES;
1054}
1055
1056#pragma mark - Properties
1057
edchin3365c7d2017-09-01 22:20:371058- (id<ApplicationCommands,
1059 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371060 OmniboxFocuser,
1061 UrlLoader,
1062 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561063 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
1064 UrlLoader, WebToolbarDelegate>>(_dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241065}
1066
sdefresnee65fd872016-12-19 13:38:131067- (void)setActive:(BOOL)active {
1068 if (_active == active) {
1069 return;
1070 }
1071 _active = active;
1072
1073 // If not active, display an activity indicator overlay over the view to
1074 // prevent interaction with the web page.
1075 // TODO(crbug.com/637093): This coordinator should be managed by the
1076 // coordinator used to present BrowserViewController, when implemented.
1077 if (active) {
1078 [self.activityOverlayCoordinator stop];
1079 self.activityOverlayCoordinator = nil;
1080 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171081 self.activityOverlayCoordinator =
1082 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131083 [self.activityOverlayCoordinator start];
1084 }
1085
1086 if (_browserState) {
1087 web::ActiveStateManager* active_state_manager =
1088 web::BrowserState::GetActiveStateManager(_browserState);
1089 active_state_manager->SetActive(active);
1090 }
1091
1092 [_model setWebUsageEnabled:active];
1093 [self updateDialogPresenterActiveState];
1094
1095 if (active) {
1096 // Make sure the tab (if any; it's possible to get here without a current
1097 // tab if the caller is about to create one) ends up on screen completely.
1098 Tab* currentTab = [_model currentTab];
1099 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441100 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131101 if (_expectingForegroundTab)
1102 [currentTab.webController setOverlayPreviewMode:YES];
1103 if (currentTab)
1104 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341105 } else {
1106 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131107 }
sdefresnee65fd872016-12-19 13:38:131108 [_paymentRequestManager enablePaymentRequest:active];
1109
1110 [self setNeedsStatusBarAppearanceUpdate];
1111}
1112
1113- (void)setPrimary:(BOOL)primary {
1114 [_model setPrimary:primary];
1115 if (primary) {
1116 [self updateDialogPresenterActiveState];
1117 } else {
1118 self.dialogPresenter.active = false;
1119 }
1120}
1121
1122- (BOOL)isPlayingTTS {
1123 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1124}
1125
sdefresne6165c8742017-01-16 15:42:021126- (ios::ChromeBrowserState*)browserState {
1127 return _browserState;
1128}
1129
1130- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271131 return _model;
sdefresne6165c8742017-01-16 15:42:021132}
1133
sdefresnee65fd872016-12-19 13:38:131134- (SideSwipeController*)sideSwipeController {
1135 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271136 _sideSwipeController =
1137 [[SideSwipeController alloc] initWithTabModel:_model
1138 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131139 [_sideSwipeController setSnapshotDelegate:self];
1140 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031141 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131142 }
1143 return _sideSwipeController;
1144}
1145
sdefresnee65fd872016-12-19 13:38:131146- (DialogPresenter*)dialogPresenter {
1147 return _dialogPresenter;
1148}
1149
sdefresnee65fd872016-12-19 13:38:131150- (BOOL)canUseDesktopUserAgent {
1151 Tab* tab = [_model currentTab];
1152 if ([self isTabNativePage:tab])
1153 return NO;
1154
1155 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441156 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131157}
1158
1159// Whether the sharing menu should be shown.
1160- (BOOL)canShowShareMenu {
kkhorimotob110b262017-06-01 18:38:251161 const GURL& URL = [_model currentTab].lastCommittedURL;
1162 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131163}
1164
1165- (BOOL)canShowFindBar {
1166 // Make sure web controller can handle find in page.
1167 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421168 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131169 return NO;
rohitrao005a6432017-03-16 20:52:421170 }
sdefresnee65fd872016-12-19 13:38:131171
rohitrao005a6432017-03-16 20:52:421172 auto* helper = FindTabHelper::FromWebState(tab.webState);
1173 return (helper && helper->CurrentPageSupportsFindInPage() &&
1174 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131175}
1176
liaoyukeea9f3ee62017-03-07 22:05:391177- (web::UserAgentType)userAgentType {
1178 web::WebState* webState = [_model currentTab].webState;
1179 if (!webState)
1180 return web::UserAgentType::NONE;
1181 web::NavigationItem* visibleItem =
1182 webState->GetNavigationManager()->GetVisibleItem();
1183 if (!visibleItem)
1184 return web::UserAgentType::NONE;
1185
1186 return visibleItem->GetUserAgentType();
1187}
1188
sdefresnee65fd872016-12-19 13:38:131189- (void)setVisible:(BOOL)visible {
1190 if (_visible == visible)
1191 return;
1192 _visible = visible;
1193}
1194
1195- (void)setViewVisible:(BOOL)viewVisible {
1196 if (_viewVisible == viewVisible)
1197 return;
1198 _viewVisible = viewVisible;
1199 self.visible = viewVisible;
1200 [self updateDialogPresenterActiveState];
1201}
1202
1203- (BOOL)isToolbarOnScreen {
1204 return [self headerHeight] - [self currentHeaderOffset] > 0;
1205}
1206
kkhorimotoa44349c12017-04-12 23:02:121207- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1208 if (_inNewTabAnimation == inNewTabAnimation)
1209 return;
1210 _inNewTabAnimation = inNewTabAnimation;
1211 [self updateDialogPresenterActiveState];
1212}
1213
sdefresnee65fd872016-12-19 13:38:131214- (BOOL)isInNewTabAnimation {
1215 return _inNewTabAnimation;
1216}
1217
1218- (BOOL)shouldShowVoiceSearchBar {
1219 // On iPads, the voice search bar should only be shown for regular horizontal
1220 // size class configurations. It should always be shown for voice search
1221 // results Tabs on iPhones, including configurations with regular horizontal
1222 // size classes (i.e. landscape iPhone 6 Plus).
1223 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1224 UIUserInterfaceSizeClassCompact;
1225 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1226 (!IsIPadIdiom() || compactWidth);
1227}
1228
1229- (void)setHideStatusBar:(BOOL)hideStatusBar {
1230 if (_hideStatusBar == hideStatusBar)
1231 return;
1232 _hideStatusBar = hideStatusBar;
1233 [self setNeedsStatusBarAppearanceUpdate];
1234}
1235
1236#pragma mark - IBActions
1237
1238- (void)shieldWasTapped:(id)sender {
1239 [_toolbarController cancelOmniboxEdit];
1240}
1241
Cooper Knaakd0a974cd2017-08-10 18:05:471242- (void)userEnteredTabSwitcher {
1243 if ([self.tabTipBubblePresenter isUserEngaged]) {
1244 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1245 }
1246}
1247
Cooper Knaake963d6702017-08-11 21:03:111248- (void)presentBubblesIfEligible {
1249 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401250 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111251}
1252
sdefresnee65fd872016-12-19 13:38:131253#pragma mark - UIViewController methods
1254
1255// Perform additional set up after loading the view, typically from a nib.
1256- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091257 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281258 initialViewsRect.origin.y += StatusBarHeight();
1259 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131260 UIViewAutoresizing initialViewAutoresizing =
1261 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1262
stkhapuginf58b10d02017-04-10 13:36:171263 self.contentArea =
1264 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131265 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171266 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131267 self.typingShield.autoresizingMask = initialViewAutoresizing;
1268 [self.typingShield addTarget:self
1269 action:@selector(shieldWasTapped:)
1270 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131271 self.view.autoresizingMask = initialViewAutoresizing;
1272 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1273 [self.view addSubview:self.contentArea];
1274 [self.view addSubview:self.typingShield];
1275 [super viewDidLoad];
1276
1277 // Install fake status bar for iPad iOS7
1278 [self installFakeStatusBar];
1279 [self buildToolbarAndTabStrip];
1280 [self setUpViewLayout];
1281 // If the tab model and browser state are valid, finish initialization.
1282 if (_model && _browserState)
1283 [self addUIFunctionalityForModelAndBrowserState];
1284
1285 // Add a tap gesture recognizer to save the last tap location for the source
1286 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271287 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1288 initWithTarget:self
1289 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131290 [tapRecognizer setDelegate:self];
1291 [tapRecognizer setCancelsTouchesInView:NO];
1292 [_contentArea addGestureRecognizer:tapRecognizer];
1293}
1294
Justin Cohenb3170c32017-09-19 01:55:221295- (void)viewSafeAreaInsetsDidChange {
1296 [super viewSafeAreaInsetsDidChange];
1297 // Gate this behind iPhone X, since it's currently the only device that
1298 // needs layout updates here after startup.
1299 if (IsIPhoneX())
1300 [self setUpViewLayout];
1301}
1302
sdefresnee65fd872016-12-19 13:38:131303- (void)viewDidAppear:(BOOL)animated {
1304 [super viewDidAppear:animated];
1305 self.viewVisible = YES;
1306 [self updateDialogPresenterActiveState];
Cooper Knaake963d6702017-08-11 21:03:111307 [self presentBubblesIfEligible];
sdefresnee65fd872016-12-19 13:38:131308}
1309
1310- (void)viewWillAppear:(BOOL)animated {
1311 [super viewWillAppear:animated];
1312
1313 // Reparent the toolbar if it's been relinquished.
1314 if (_isToolbarControllerRelinquished)
1315 [self reparentToolbarController];
1316
1317 self.visible = YES;
1318
1319 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251320 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131321 _infoBarContainer->RestoreInfobars();
1322 }
1323
1324 // If the controller is suspended, or has been paged out due to low memory,
1325 // updating the view will be handled when it's displayed again.
1326 if (![_model webUsageEnabled] || !self.contentArea)
1327 return;
1328 // Update the displayed tab (if any; the switcher may not have created one
1329 // yet) in case it changed while showing the switcher.
1330 Tab* currentTab = [_model currentTab];
1331 if (currentTab)
1332 [self displayTab:currentTab isNewSelection:YES];
1333}
1334
1335- (void)viewWillDisappear:(BOOL)animated {
1336 self.viewVisible = NO;
1337 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131338 [[_model currentTab] wasHidden];
1339 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251340 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131341 _infoBarContainer->SuspendInfobars();
1342 }
1343 [super viewWillDisappear:animated];
1344}
1345
1346- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1347 duration:(NSTimeInterval)duration {
1348 [super willRotateToInterfaceOrientation:orient duration:duration];
1349 [self dismissPopups];
1350 [self reshowFindBarIfNeededWithCoordinator:nil];
1351}
1352
1353- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1354 [super didRotateFromInterfaceOrientation:orient];
1355
1356 // This reinitializes the toolbar, including updating the Overlay View,
1357 // if there is one.
1358 [self updateToolbar];
1359 [self infoBarContainerStateChanged:false];
1360}
1361
1362- (BOOL)prefersStatusBarHidden {
1363 return self.hideStatusBar;
1364}
1365
1366// Called when in the foreground and the OS needs more memory. Release as much
1367// as possible.
1368- (void)didReceiveMemoryWarning {
1369 // Releases the view if it doesn't have a superview.
1370 [super didReceiveMemoryWarning];
1371
1372 // Release any cached data, images, etc that aren't in use.
1373 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1374 // but since the FaviconCache uses obj-c in the header, it can't be included
1375 // there.
1376 if (_browserState) {
1377 FaviconLoader* loader =
1378 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1379 _browserState);
1380 if (loader)
1381 loader->PurgeCache();
1382 }
1383
1384 if (![self isViewLoaded]) {
1385 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1386 // as the BrowserViewController.
1387 self.contentArea = nil;
1388 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271389 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131390 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271391 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421392 self.recentTabsCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271393 _toolbarController = nil;
1394 _toolbarModelDelegate = nil;
1395 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031396 [self.tabStripCoordinator stop];
1397 self.tabStripCoordinator = nil;
1398 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271399 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131400 }
1401}
1402
1403- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1404 [super traitCollectionDidChange:previousTraitCollection];
1405 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1406 // because in some cases the presented view controller isn't a child of the
1407 // BVC in the view controller hierarchy (some intervening object isn't a
1408 // view controller).
1409 [self.presentedViewController
1410 traitCollectionDidChange:previousTraitCollection];
1411 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1412 // Update voice search bar visibility.
1413 [self updateVoiceSearchBarVisibilityAnimated:NO];
1414}
1415
1416- (void)viewWillTransitionToSize:(CGSize)size
1417 withTransitionCoordinator:
1418 (id<UIViewControllerTransitionCoordinator>)coordinator {
1419 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1420 [self dismissPopups];
1421 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1422}
1423
1424- (void)reshowFindBarIfNeededWithCoordinator:
1425 (id<UIViewControllerTransitionCoordinator>)coordinator {
1426 if (![_findBarController isFindInPageShown])
1427 return;
1428
1429 // Record focused state.
1430 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1431
1432 [self hideFindBarWithAnimation:NO];
1433
stkhapuginc9eee7b2017-04-10 15:49:271434 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131435 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1436 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271437 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131438 if (strongSelf)
1439 [strongSelf showFindBarWithAnimation:NO
1440 selectText:NO
1441 shouldFocus:isFocusedBeforeReshow];
1442 };
1443
1444 BOOL enqueued =
1445 [coordinator animateAlongsideTransition:nil completion:completion];
1446 if (!enqueued) {
1447 completion(nil);
1448 }
1449}
1450
1451- (void)dismissViewControllerAnimated:(BOOL)flag
1452 completion:(void (^)())completion {
1453 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271454 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131455 [super dismissViewControllerAnimated:flag
1456 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271457 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131458 [strongSelf setDismissingModal:NO];
1459 [strongSelf setPresenting:NO];
1460 if (completion)
1461 completion();
1462 [[strongSelf dialogPresenter] tryToPresent];
1463 }];
1464}
1465
1466- (void)presentViewController:(UIViewController*)viewControllerToPresent
1467 animated:(BOOL)flag
1468 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271469 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131470 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1471 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1472 // presented, to show a temporary view of the launch screen and then remove it
1473 // when the controller for the FRE has been presented. This fix should be
1474 // removed when the FRE startup code is rewritten.
1475 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1476 experimental_flags::AlwaysDisplayFirstRun()) &&
1477 !tests_hook::DisableFirstRun();
1478 // These if statements check that |presentViewController| is being called for
1479 // the FRE case.
1480 if (firstRunLaunch &&
1481 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1482 UINavigationController* navController =
1483 base::mac::ObjCCastStrict<UINavigationController>(
1484 viewControllerToPresent);
1485 if ([navController.topViewController
1486 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1487 self.hideStatusBar = YES;
1488
1489 // Load view from Launch Screen and add it to window.
1490 NSBundle* mainBundle = base::mac::FrameworkBundle();
1491 NSArray* topObjects =
1492 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1493 UIViewController* launchScreenController =
1494 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1495 // |launchScreenView| is loaded as an autoreleased object, and is retained
1496 // by the |completion| block below.
1497 UIView* launchScreenView = launchScreenController.view;
1498 launchScreenView.userInteractionEnabled = NO;
1499 launchScreenView.frame = self.view.window.bounds;
1500 [self.view.window addSubview:launchScreenView];
1501
1502 // Replace the completion handler sent to the superclass with one which
1503 // removes |launchScreenView| and resets the status bar. If |completion|
1504 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271505 __weak BrowserViewController* weakSelf = self;
1506 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131507 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271508 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131509 if (completion)
1510 completion();
stkhapuginc9eee7b2017-04-10 15:49:271511 };
sdefresnee65fd872016-12-19 13:38:131512 }
1513 }
1514
1515 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171516 if ([_sideSwipeController inSwipe]) {
1517 [_sideSwipeController resetContentView];
1518 }
sdefresnee65fd872016-12-19 13:38:131519
1520 [super presentViewController:viewControllerToPresent
1521 animated:flag
1522 completion:finalCompletionHandler];
1523}
1524
1525#pragma mark - Notification handling
1526
1527- (void)registerForNotifications {
1528 DCHECK(_model);
1529 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1530 [defaultCenter addObserver:self
1531 selector:@selector(pageLoadStarting:)
1532 name:kTabModelTabWillStartLoadingNotification
1533 object:_model];
1534 [defaultCenter addObserver:self
1535 selector:@selector(pageLoadStarted:)
1536 name:kTabModelTabDidStartLoadingNotification
1537 object:_model];
1538 [defaultCenter addObserver:self
1539 selector:@selector(pageLoadComplete:)
1540 name:kTabModelTabDidFinishLoadingNotification
1541 object:_model];
1542 [defaultCenter addObserver:self
1543 selector:@selector(tabDeselected:)
1544 name:kTabModelTabDeselectedNotification
1545 object:_model];
1546 [defaultCenter addObserver:self
1547 selector:@selector(tabWasAdded:)
1548 name:kTabModelNewTabWillOpenNotification
1549 object:_model];
1550}
1551
1552- (void)pageLoadStarting:(NSNotification*)notify {
1553 Tab* tab = notify.userInfo[kTabModelTabKey];
1554 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511555
1556 // Stop any Find in Page searches and close the find bar when navigating to a
1557 // new page.
1558 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361559
sdefresnee65fd872016-12-19 13:38:131560 if (tab == [_model currentTab]) {
1561 // TODO(pinkerton): Fill in here about hiding the forward button on
1562 // navigation.
1563 }
1564}
1565
1566- (void)pageLoadStarted:(NSNotification*)notify {
1567 Tab* tab = notify.userInfo[kTabModelTabKey];
1568 DCHECK(tab);
1569 if (tab == [_model currentTab]) {
1570 if (![self isTabNativePage:tab]) {
1571 [_toolbarController currentPageLoadStarted];
1572 }
1573 [self updateVoiceSearchBarVisibilityAnimated:NO];
1574 }
1575}
1576
1577- (void)pageLoadComplete:(NSNotification*)notify {
1578 // Update the UI, but only if the current tab.
1579 Tab* tab = notify.userInfo[kTabModelTabKey];
1580 if (tab == [_model currentTab]) {
1581 // There isn't any need to update the toolbar here. When the page finishes,
1582 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1583 }
1584
1585 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1586
1587 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1588}
1589
1590- (void)tabDeselected:(NSNotification*)notify {
1591 DCHECK(notify);
1592 Tab* tab = notify.userInfo[kTabModelTabKey];
1593 DCHECK(tab);
1594 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221595 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131596}
1597
1598- (void)tabWasAdded:(NSNotification*)notify {
1599 Tab* tab = notify.userInfo[kTabModelTabKey];
1600 DCHECK(tab);
1601
Eugene But56efc322017-08-11 14:03:441602 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131603
1604 // When adding new tabs, check what kind of reminder infobar should
1605 // be added to the new tab. Try to add only one of them.
1606 // This check is done when a new tab is added either through the Tools Menu
1607 // "New Tab" or through "New Tab" in Stack View Controller. This method
1608 // is called after a new tab has added and finished initial navigation.
1609 // If this is added earlier, the initial navigation may end up clearing
1610 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301611 web::WebState* webState = tab.webState;
1612 DCHECK(webState);
1613
1614 infobars::InfoBarManager* infoBarManager =
1615 InfoBarManagerImpl::FromWebState(webState);
1616 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131617 forTabId:[tab tabId]];
edchinbb8ba892017-09-12 15:44:031618 if (!ReSignInInfoBarDelegate::Create(_browserState, tab, self.dispatcher)) {
edchin9cad67b2017-09-11 20:13:571619 DisplaySyncErrors(_browserState, tab, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:131620 }
1621
1622 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501623 // phone-specific. Call the foreground tab added completion block; for
1624 // iPhones, this will get executed after the animation has finished.
1625 if (IsIPadIdiom()) {
1626 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431627 // This callback is called before webState is activated (on
1628 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1629 // callback asynchronously to be sure the activation is complete.
1630 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011631 // Test existence again as the block may have been deleted.
1632 if (self.foregroundTabWasAddedCompletionBlock) {
1633 self.foregroundTabWasAddedCompletionBlock();
1634 self.foregroundTabWasAddedCompletionBlock = nil;
1635 }
Olivier Robinc7e46242017-09-06 07:55:431636 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501637 }
sdefresnee65fd872016-12-19 13:38:131638 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501639 }
sdefresnee65fd872016-12-19 13:38:131640
1641 // Do nothing if browsing is currently suspended. The BVC will set everything
1642 // up correctly when browsing resumes.
1643 if (!self.visible || ![_model webUsageEnabled])
1644 return;
1645
1646 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1647
1648 // Block that starts voice search at the end of new Tab animation if
1649 // necessary.
1650 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1651 if (_startVoiceSearchAfterNewTabAnimation) {
1652 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001653 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131654 }
1655 };
1656
kkhorimotoa44349c12017-04-12 23:02:121657 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131658 if (!inBackground) {
1659 UIView* animationParentView = _contentArea;
1660 // Create the new page image, and load with the new tab page snapshot.
1661 CGFloat newPageOffset = 0;
1662 UIImageView* newPage;
kkhorimotob110b262017-06-01 18:38:251663 if (tab.lastCommittedURL == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
sdefresnee65fd872016-12-19 13:38:131664 !IsIPadIdiom()) {
1665 animationParentView = self.view;
1666 newPage = [self pageFullScreenOpenCloseAnimationView];
1667 } else {
1668 newPage = [self pageOpenCloseAnimationView];
1669 }
1670 newPageOffset = newPage.frame.origin.y;
1671
1672 [tab view].frame = _contentArea.bounds;
1673 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1674 [animationParentView addSubview:newPage];
1675 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521676 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131677 newPage, -newPageOffset,
1678 newPage.frame.size.height - newPage.image.size.height, origin,
1679 _isOffTheRecord, NULL, ^{
1680 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121681 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421682 // Use the model's currentTab here because it is possible that it can
1683 // be reset to a new value before the new Tab animation finished (e.g.
1684 // if another Tab shows a dialog via |dialogPresenter|). However, that
1685 // tab's view hasn't been displayed yet because it was in a new tab
1686 // animation.
1687 Tab* currentTab = [_model currentTab];
1688 if (currentTab) {
1689 [self tabSelected:currentTab];
1690 }
sdefresnee65fd872016-12-19 13:38:131691 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391692
1693 if (self.foregroundTabWasAddedCompletionBlock) {
1694 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451695 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391696 }
sdefresnee65fd872016-12-19 13:38:131697 });
1698 } else {
1699 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1700 // snapshot before adding the views needed for the background animation.
1701 Tab* topTab = [_model currentTab];
1702 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1703 visibleFrameOnly:self.isToolbarOnScreen];
1704 // Add three layers in order on top of the contentArea for the animation:
1705 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271706 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131707 InstallBackgroundInView(background);
1708 [_contentArea addSubview:background];
1709
1710 // 2. A CardView displaying the data from the current tab.
1711 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1712 NSString* title = [topTab title];
1713 if (![title length])
1714 title = [topTab urlDisplayString];
1715 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131716 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371717 [topCard setFavicon:nil];
1718
1719 favicon::FaviconDriver* faviconDriver =
1720 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1721 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1722 gfx::Image favicon = faviconDriver->GetFavicon();
1723 if (!favicon.IsEmpty())
1724 [topCard setFavicon:favicon.ToUIImage()];
1725 }
sdefresnee65fd872016-12-19 13:38:131726
1727 // 3. A new, blank CardView to represent the new tab being added.
1728 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521729 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131730 topCard, [_contentArea frame], IsPortrait(), ^{
1731 [background removeFromSuperview];
1732 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121733 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131734 // Resnapshot the top card if it has its own toolbar, as the toolbar
1735 // will be captured in the new tab animation, but isn't desired for
1736 // the stack view snapshots.
1737 id nativeController = [self nativeControllerForTab:topTab];
1738 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1739 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1740 startVoiceSearchIfNecessaryBlock();
1741 });
peterlaurens9f1b6e02017-06-22 17:46:451742 // Reset the foreground tab completion block so that it can never be
1743 // called more than once regardless of foreground/background tab
1744 // appearances.
1745 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131746 }
1747}
1748
1749#pragma mark - UI Configuration and Layout
1750
1751- (void)updateWithTabModel:(TabModel*)model
1752 browserState:(ios::ChromeBrowserState*)browserState {
1753 DCHECK(model);
1754 DCHECK(browserState);
1755 DCHECK(!_model);
1756 DCHECK(!_browserState);
1757 _browserState = browserState;
1758 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271759 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381760
sdefresnee65fd872016-12-19 13:38:131761 [_model addObserver:self];
1762
1763 if (!_isOffTheRecord) {
1764 [DefaultIOSWebViewFactory
1765 registerWebViewFactory:[ChromeWebViewFactory class]];
1766 }
1767 NSUInteger count = [_model count];
1768 for (NSUInteger index = 0; index < count; ++index)
1769 [self installDelegatesForTab:[_model tabAtIndex:index]];
1770
1771 [self registerForNotifications];
1772
gambardbdc07cc2017-02-03 16:43:111773 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121774 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271775 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131776
sdefresnedc432f42017-01-17 14:36:591777 // Register for bookmark changed notification (BookmarkModel may be null
1778 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131779 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591780 if (_bookmarkModel) {
1781 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1782 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1783 }
sdefresnee65fd872016-12-19 13:38:131784}
1785
sdefresnee65fd872016-12-19 13:38:131786- (void)browserStateDestroyed {
1787 [self setActive:NO];
Gauthier Ambard8160349c2017-09-06 14:43:241788 [self setToolbarBackgroundAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:131789 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271790 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131791 [_toolbarController browserStateDestroyed];
1792 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381793
1794 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431795 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111796 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381797 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271798 [_pageInfoCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031799 [self.tabStripCoordinator stop];
1800 self.tabStripCoordinator = nil;
1801 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381802
Eugene Butf2d7fee2017-09-16 01:27:551803 // The file remover needs the browser state, so needs to be destroyed now.
1804 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131805 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391806 [_dispatcher stopDispatchingToTarget:self];
1807 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131808}
1809
1810- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221811 CGFloat statusBarHeight = StatusBarHeight();
1812 CGRect statusBarFrame =
1813 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1814 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1815 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131816 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221817 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1818 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1819 [_fakeStatusBarView layer].zPosition = 99;
1820 [[self view] addSubview:_fakeStatusBarView];
1821 } else {
1822 // Add a white bar on phone so that the status bar on the NTP is white.
1823 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1824 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131825 }
1826}
1827
1828// Create the UI elements. May or may not have valid browser state & tab model.
1829- (void)buildToolbarAndTabStrip {
1830 DCHECK([self isViewLoaded]);
1831 DCHECK(!_toolbarModelDelegate);
1832
Rohit Rao44f204302017-08-10 14:49:541833 // Initialize the prerender service before creating the toolbar controller.
1834 PrerenderService* prerenderService =
1835 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1836 if (prerenderService) {
1837 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131838 }
1839
1840 // Create the toolbar model and controller.
Rohit Rao44f204302017-08-10 14:49:541841 id<PreloadProvider> preloadProvider =
1842 prerenderService ? prerenderService->GetPreloadProvider() : nil;
rohitrao8c4c7fd2017-04-03 15:31:201843 _toolbarModelDelegate.reset(
1844 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131845 _toolbarModelIOS.reset([_dependencyFactory
1846 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Mark Cogan6ebbde02017-07-07 12:50:131847 _toolbarController =
1848 [_dependencyFactory newWebToolbarControllerWithDelegate:self
1849 urlLoader:self
Rohit Rao44f204302017-08-10 14:49:541850 preloadProvider:preloadProvider
Mark Cogan6ebbde02017-07-07 12:50:131851 dispatcher:self.dispatcher];
justincohen75011c32017-04-28 16:31:391852 [_dispatcher startDispatchingToTarget:_toolbarController
1853 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131854 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271855 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131856 _voiceSearchController->SetDelegate(_toolbarController);
1857
sdefresnee65fd872016-12-19 13:38:131858 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031859 self.tabStripCoordinator =
1860 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1861 self.tabStripCoordinator.browserState = _browserState;
1862 self.tabStripCoordinator.dispatcher = _dispatcher;
1863 self.tabStripCoordinator.tabModel = _model;
1864 self.tabStripCoordinator.presentationProvider = self;
1865 self.tabStripCoordinator.animationWaitDuration =
1866 kFullScreenControllerToolbarAnimationDuration;
1867 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131868 }
1869
1870 // Create infobar container.
1871 if (!_infoBarContainerDelegate) {
1872 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1873 _infoBarContainer.reset(
1874 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1875 }
1876}
1877
1878// Enable functionality that only makes sense if the views are loaded and
1879// both browser state and tab model are valid.
1880- (void)addUIFunctionalityForModelAndBrowserState {
1881 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051882 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131883 DCHECK(_model);
1884 DCHECK([self isViewLoaded]);
1885
1886 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1887
Rohit Raoaf46af92017-08-10 12:52:301888 infobars::InfoBarManager* infoBarManager = nullptr;
1889 if (_model.currentTab) {
1890 DCHECK(_model.currentTab.webState);
1891 infoBarManager =
1892 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1893 }
sdefresnee65fd872016-12-19 13:38:131894 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1895
sczsdd860eba2017-08-10 01:55:381896 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431897 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1898 initWithBaseViewController:self];
1899 _activityServiceCoordinator.dispatcher = _dispatcher;
1900 _activityServiceCoordinator.tabModel = _model;
1901 _activityServiceCoordinator.browserState = _browserState;
1902 _activityServiceCoordinator.positionProvider = _toolbarController;
1903 _activityServiceCoordinator.presentationProvider = self;
1904 _activityServiceCoordinator.snackbarProvider = self;
1905
Rohit Raocda0a992017-08-16 15:37:111906 _qrScannerCoordinator =
1907 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1908 _qrScannerCoordinator.dispatcher = _dispatcher;
1909 _qrScannerCoordinator.loadProvider = _toolbarController;
1910 _qrScannerCoordinator.presentationProvider = self;
1911
sczsdd860eba2017-08-10 01:55:381912 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131913 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381914 _tabHistoryCoordinator.dispatcher = _dispatcher;
1915 _tabHistoryCoordinator.positionProvider = _toolbarController;
1916 _tabHistoryCoordinator.tabModel = _model;
1917 _tabHistoryCoordinator.presentationProvider = self;
1918 _tabHistoryCoordinator.tabHistoryUIUpdater = _toolbarController;
1919
sczs6ae47ad2017-09-06 17:26:531920 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
1921 _sadTabCoordinator.dispatcher = _dispatcher;
1922
Gregory Chatzinoffdf93d692017-09-09 01:32:271923 _pageInfoCoordinator =
1924 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
1925 _pageInfoCoordinator.browserState = _browserState;
1926 _pageInfoCoordinator.dispatcher = _dispatcher;
1927 _pageInfoCoordinator.loader = self;
1928 _pageInfoCoordinator.presentationProvider = self;
1929 _pageInfoCoordinator.tabModel = _model;
1930
mathp9b4c11d2017-07-06 20:24:131931 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:271932 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131933 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:201934 browserState:_browserState
1935 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:051936 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:191937 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:131938 }
1939}
1940
1941// Set the frame for the various views. View must be loaded.
1942- (void)setUpViewLayout {
1943 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:131944 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:131945 CGFloat minY = [self headerOffset];
1946
Justin Cohenb3170c32017-09-19 01:55:221947 // Update the fake toolbar background height.
1948 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
1949 fakeStatusBarFrame.size.height = StatusBarHeight();
1950 _fakeStatusBarView.frame = fakeStatusBarFrame;
1951
edchinf5150c682017-09-18 02:50:031952 if (self.tabStripView) {
1953 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:131954 }
1955
1956 // Position the toolbar next, either at the top of the browser view or
1957 // directly under the tabstrip.
1958 CGRect toolbarFrame = [[_toolbarController view] frame];
1959 toolbarFrame.origin = CGPointMake(0, minY);
1960 toolbarFrame.size.width = widthOfView;
1961 [[_toolbarController view] setFrame:toolbarFrame];
1962
1963 // Place the infobar container above the content area.
1964 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1965 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1966
1967 // Place the toolbar controller above the infobar container.
1968 [[self view] insertSubview:[_toolbarController view]
1969 aboveSubview:infoBarContainerView];
1970 minY += CGRectGetHeight(toolbarFrame);
1971
1972 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1973 // content slightly.
1974 minY -= [ToolbarController toolbarDropShadowHeight];
1975
1976 // Adjust the content area to be under the toolbar, for fullscreen or below
1977 // the toolbar is not fullscreen.
1978 CGRect contentFrame = [_contentArea frame];
1979 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:221980 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
1981 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:131982 [_contentArea setFrame:contentFrame];
1983
1984 // Adjust the infobar container to be either at the bottom of the screen
1985 // (iPhone) or on the lower toolbar edge (iPad).
1986 CGRect infoBarFrame = contentFrame;
1987 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1988 infoBarFrame.size.height = 0;
1989 [infoBarContainerView setFrame:infoBarFrame];
1990
1991 // Attach the typing shield to the content area but have it hidden.
1992 [_typingShield setFrame:[_contentArea frame]];
1993 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1994 [_typingShield setHidden:YES];
1995 _typingShield.accessibilityIdentifier = @"Typing Shield";
1996 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1997}
1998
sdefresnee65fd872016-12-19 13:38:131999- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2000 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442001 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132002
kkhorimotoa44349c12017-04-12 23:02:122003 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132004 // Hide findbar. |updateToolbar| will restore the findbar later.
2005 [self hideFindBarWithAnimation:NO];
2006
2007 // Make new content visible, resizing it first as the orientation may
2008 // have changed from the last time it was displayed.
2009 [[tab view] setFrame:_contentArea.bounds];
2010 [_contentArea displayContentView:[tab view]];
2011 }
2012 [self updateToolbar];
2013
2014 if (newSelection)
2015 [_toolbarController selectedTabChanged];
2016
2017 // Notify the Tab that it was displayed.
2018 [tab wasShown];
2019}
2020
2021- (void)initializeBookmarkInteractionController {
2022 if (_bookmarkInteractionController)
2023 return;
edchinbb8ba892017-09-12 15:44:032024 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2025 initWithBrowserState:_browserState
2026 loader:self
2027 parentController:self
2028 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132029}
2030
2031// Update the state of back and forward buttons, hiding the forward button if
2032// there is nowhere to go. Assumes the model's current tab is up to date.
2033- (void)updateToolbar {
2034 // If the BVC has been partially torn down for low memory, wait for the
2035 // view rebuild to handle toolbar updates.
2036 if (!(_toolbarModelIOS && _browserState))
2037 return;
2038
2039 Tab* tab = [_model currentTab];
2040 if (![tab navigationManager])
2041 return;
2042 [_toolbarController updateToolbarState];
2043 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
2044
Rohit Rao44f204302017-08-10 14:49:542045 PrerenderService* prerenderService =
2046 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2047 BOOL isPrerenderTab =
2048 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2049 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sdefresnee65fd872016-12-19 13:38:132050 [_toolbarController showPrerenderingAnimation];
2051
2052 // Also update the loading state for the tools menu (that is really an
2053 // extension of the toolbar on the iPhone).
2054 if (!IsIPadIdiom())
2055 [[_toolbarController toolsPopupController]
2056 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2057
rohitrao005a6432017-03-16 20:52:422058 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2059 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132060 [self showFindBarWithAnimation:NO
2061 selectText:YES
2062 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362063 }
sdefresnee65fd872016-12-19 13:38:132064
2065 // Hide the toolbar if displaying phone NTP.
2066 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552067 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132068 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552069 if (item) {
2070 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132071 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2072 hideToolbar = isNTP && !_isOffTheRecord &&
2073 ![_toolbarController isOmniboxFirstResponder] &&
2074 ![_toolbarController showingOmniboxPopup];
2075 }
2076 [[_toolbarController view] setHidden:hideToolbar];
2077 }
2078}
2079
2080- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122081 self.dialogPresenter.active =
2082 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132083}
2084
2085- (void)dismissPopups {
jif7fed8122017-02-08 13:15:252086 [_toolbarController dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272087 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:472088 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382089}
2090
Cooper Knaakd0a974cd2017-08-10 18:05:472091- (BubbleViewControllerPresenter*)
2092bubblePresenterForFeature:(const base::Feature&)feature
2093 direction:(BubbleArrowDirection)direction
2094 alignment:(BubbleAlignment)alignment
2095 text:(NSString*)text {
Cooper Knaak33f9f402017-08-09 18:04:382096 if (!feature_engagement::TrackerFactory::GetForBrowserState(_browserState)
2097 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472098 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382099 }
2100 // Capture |weakSelf| instead of the feature engagement tracker object
2101 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2102 // the feature engagement tracker will remain pointing to invalid memory if
2103 // its owner (the ChromeBrowserState) is deallocated.
2104 __weak BrowserViewController* weakSelf = self;
2105 void (^dismissalCallback)(void) = ^() {
2106 BrowserViewController* strongSelf = weakSelf;
2107 if (strongSelf) {
2108 feature_engagement::TrackerFactory::GetForBrowserState(
2109 strongSelf.browserState)
2110 ->Dismissed(feature);
2111 }
2112 };
2113
Cooper Knaakd0a974cd2017-08-10 18:05:472114 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382115 [[BubbleViewControllerPresenter alloc] initWithText:text
2116 arrowDirection:direction
2117 alignment:alignment
2118 dismissalCallback:dismissalCallback];
2119
Cooper Knaakd0a974cd2017-08-10 18:05:472120 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132121}
2122
Cooper Knaak120cee5e2017-08-10 20:57:002123- (void)presentNewTabTipBubbleOnInitialized {
2124 // If the tab tip bubble has already been presented and the user is still
2125 // considered engaged, it can't be overwritten or set to |nil| or else it will
2126 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2127 // can be safely overwritten or set to |nil|.
2128 if (!self.tabTipBubblePresenter.isUserEngaged) {
2129 __weak BrowserViewController* weakSelf = self;
2130 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2131 [weakSelf presentNewTabTipBubble];
2132 };
2133
2134 // Because the new tab tip occurs on startup, the feature engagement
2135 // tracker's database is not guaranteed to be loaded by this time. For the
2136 // bubble to appear properly, a callback is used to guarantee the event data
2137 // is loaded before the check to see if the promotion should be displayed.
2138 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2139 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2140 }
2141}
2142
2143- (void)presentNewTabTipBubble {
2144 NSString* text =
2145 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2146 CGPoint tabSwitcherAnchor;
2147 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032148 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002149 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032150 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002151 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2152 } else {
2153 DCHECK([self.toolbarController
2154 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
2155 tabSwitcherAnchor = [self.toolbarController
2156 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2157 }
Cooper Knaake963d6702017-08-11 21:03:112158 // If the feature engagement tracker does not consider it valid to display
2159 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2160 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002161 self.tabTipBubblePresenter =
2162 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2163 direction:BubbleArrowDirectionUp
2164 alignment:BubbleAlignmentTrailing
2165 text:text];
2166 [self.tabTipBubblePresenter presentInViewController:self
2167 view:self.view
2168 anchorPoint:tabSwitcherAnchor];
2169}
2170
Helen Yang9175bd52017-08-12 00:28:402171- (void)presentNewIncognitoTabTipBubbleOnInitialized {
2172 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2173 // user is still considered engaged.
2174 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2175 __weak BrowserViewController* weakSelf = self;
2176 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2177 [weakSelf presentNewIncognitoTabTipBubble];
2178 };
2179
2180 // Use a callback in case the new incognito tab tip should be shown on
2181 // startup. This ensures that the tracker's database will be fully loaded
2182 // before checking if the promotion should be displayed.
2183 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2184 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2185 }
2186}
2187
2188- (void)presentNewIncognitoTabTipBubble {
2189 DCHECK([self.toolbarController
2190 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2191 NSString* text = l10n_util::GetNSStringWithFixup(
2192 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
2193 CGPoint toolsButtonAnchor = [self.toolbarController
2194 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2195 self.incognitoTabTipBubblePresenter =
2196 [self bubblePresenterForFeature:feature_engagement::
2197 kIPHNewIncognitoTabTipFeature
2198 direction:BubbleArrowDirectionUp
2199 alignment:BubbleAlignmentTrailing
2200 text:text];
2201 [self.incognitoTabTipBubblePresenter
2202 presentInViewController:self
2203 view:self.view
2204 anchorPoint:toolsButtonAnchor];
2205 // Only trigger the tools menu button animation if the bubble is shown.
2206 if (self.incognitoTabTipBubblePresenter) {
2207 [self.toolbarController triggerToolsMenuButtonAnimation];
2208 }
2209}
2210
sdefresnee65fd872016-12-19 13:38:132211#pragma mark - Tap handling
2212
Mark Cogandfcdea72017-07-18 13:47:382213- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032214 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2215 _lastTapPoint = CGPointZero;
2216 } else {
2217 _lastTapPoint =
2218 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132219 }
Mark Cogane01ebce2017-07-12 19:31:032220 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132221}
2222
2223- (CGPoint)lastTapPoint {
2224 if (CACurrentMediaTime() - _lastTapTime < 1) {
2225 return _lastTapPoint;
2226 }
2227 return CGPointZero;
2228}
2229
2230- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2231 UIView* view = gestureRecognizer.view;
2232 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2233 _lastTapPoint =
2234 [[view superview] convertPoint:viewCoordinate toView:self.view];
2235 _lastTapTime = CACurrentMediaTime();
2236}
2237
2238- (BOOL)addTabIfNoTabWithNormalBrowserState {
2239 if (![_model count]) {
2240 if (!_isOffTheRecord) {
2241 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2242 transition:ui::PAGE_TRANSITION_TYPED];
2243 return YES;
2244 }
2245 }
2246 return NO;
2247}
2248
2249#pragma mark - Tab creation and selection
2250
2251// Called when either a tab finishes loading or when a tab with finished content
2252// is added directly to the model via pre-rendering.
2253- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2254 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2255
2256 // Persist the session on a delay.
2257 [_model saveSessionImmediately:NO];
2258}
2259
2260- (Tab*)addSelectedTabWithURL:(const GURL&)url
2261 postData:(TemplateURLRef::PostContent*)postData
2262 transition:(ui::PageTransition)transition {
2263 return [self addSelectedTabWithURL:url
2264 postData:postData
2265 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492266 transition:transition
2267 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132268}
2269
2270- (Tab*)addSelectedTabWithURL:(const GURL&)url
2271 transition:(ui::PageTransition)transition {
2272 return [self addSelectedTabWithURL:url
2273 atIndex:[_model count]
2274 transition:transition];
2275}
2276
2277- (Tab*)addSelectedTabWithURL:(const GURL&)url
2278 atIndex:(NSUInteger)position
2279 transition:(ui::PageTransition)transition {
2280 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492281 atIndex:position
2282 transition:transition
2283 tabAddedCompletion:nil];
2284}
2285
2286- (Tab*)addSelectedTabWithURL:(const GURL&)url
2287 atIndex:(NSUInteger)position
2288 transition:(ui::PageTransition)transition
2289 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2290 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132291 postData:NULL
2292 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492293 transition:transition
2294 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132295}
2296
2297- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2298 postData:(TemplateURLRef::PostContent*)postData
2299 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492300 transition:(ui::PageTransition)transition
2301 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132302 if (position == NSNotFound)
2303 position = [_model count];
2304 DCHECK(position <= [_model count]);
2305
2306 web::NavigationManager::WebLoadParams params(URL);
2307 params.transition_type = transition;
2308 if (postData) {
2309 // Extract the content type and post params from |postData| and add them
2310 // to the load params.
2311 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2312 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2313 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172314 params.post_data.reset(data);
2315 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132316 }
Olivier Robind508a5632017-07-19 16:29:492317
2318 if (tabAddedCompletion) {
2319 if (self.foregroundTabWasAddedCompletionBlock) {
2320 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2321 self.foregroundTabWasAddedCompletionBlock;
2322 self.foregroundTabWasAddedCompletionBlock = ^{
2323 oldForegroundTabWasAddedCompletionBlock();
2324 tabAddedCompletion();
2325 };
2326 } else {
2327 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2328 }
2329 }
2330
sdefresnea6395912017-03-01 01:14:352331 Tab* tab = [_model insertTabWithLoadParams:params
2332 opener:nil
2333 openedByDOM:NO
2334 atIndex:position
2335 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132336 return tab;
2337}
2338
olivierrobin889af53f2017-03-01 14:56:322339// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132340- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322341 web::WebState* webState = tab.webState;
2342 if (!webState)
2343 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392344 web::NavigationItem* visibleItem =
2345 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322346 if (!visibleItem)
2347 return NO;
2348 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132349}
2350
2351- (void)expectNewForegroundTab {
2352 _expectingForegroundTab = YES;
2353}
2354
2355- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2356 CGRect viewBounds, remainder;
2357 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2358 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172359 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132360}
2361
2362- (UIImageView*)pageOpenCloseAnimationView {
2363 CGRect frame = [_contentArea bounds];
2364
2365 frame.size.height = frame.size.height - [self headerHeight];
2366 frame.origin.y = [self headerHeight];
2367
stkhapuginf58b10d02017-04-10 13:36:172368 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132369 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2370 pageView.center = center;
2371
2372 pageView.backgroundColor = [UIColor whiteColor];
2373 return pageView;
2374}
2375
2376- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142377 // Unregistration happens when the Tab is removed from the TabModel.
Mike Doughertya1ec26402017-08-23 19:46:312378 tab.iOSCaptivePortalBlockingPageDelegate = self;
Mark Cogandfcdea72017-07-18 13:47:382379 tab.dispatcher = self.dispatcher;
sdefresnee65fd872016-12-19 13:38:132380 tab.dialogDelegate = self;
2381 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132382 tab.passKitDialogProvider = self;
2383 tab.fullScreenControllerDelegate = self;
2384 if (!IsIPadIdiom()) {
2385 tab.overscrollActionsControllerDelegate = self;
2386 }
olivierrobin9ce77b82017-01-12 17:29:192387 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132388 tab.tabSnapshottingDelegate = self;
2389 // Install the proper CRWWebController delegates.
2390 tab.webController.nativeProvider = self;
2391 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512392 // BrowserViewController presents SKStoreKitViewController on behalf of a
2393 // tab.
2394 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2395 if (tabHelper)
2396 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132397 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532398 // BrowserViewController owns the coordinator that displays the Sad Tab.
2399 if (!SadTabTabHelper::FromWebState(tab.webState))
2400 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
Sylvain Defresnecacc3a52017-09-12 13:51:042401 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592402 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572403 NetExportTabHelper::CreateForWebState(tab.webState, self);
sdefresnee65fd872016-12-19 13:38:132404}
2405
sdefresne49cf2862017-03-15 13:46:142406- (void)uninstallDelegatesForTab:(Tab*)tab {
Mike Doughertya1ec26402017-08-23 19:46:312407 tab.iOSCaptivePortalBlockingPageDelegate = nil;
Mark Cogandfcdea72017-07-18 13:47:382408 tab.dispatcher = nil;
sdefresne49cf2862017-03-15 13:46:142409 tab.dialogDelegate = nil;
2410 tab.snapshotOverlayProvider = nil;
2411 tab.passKitDialogProvider = nil;
2412 tab.fullScreenControllerDelegate = nil;
2413 if (!IsIPadIdiom()) {
2414 tab.overscrollActionsControllerDelegate = nil;
2415 }
2416 tab.tabHeadersDelegate = nil;
2417 tab.tabSnapshottingDelegate = nil;
2418 tab.webController.nativeProvider = nil;
2419 tab.webController.swipeRecognizerProvider = nil;
2420 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2421 if (tabHelper)
2422 tabHelper->SetLauncher(nil);
2423 tab.webState->SetDelegate(nullptr);
2424}
2425
sdefresnee65fd872016-12-19 13:38:132426// Called when a tab is selected in the model. Make any required view changes.
2427// The notification will not be sent when the tab is already the selected tab.
2428- (void)tabSelected:(Tab*)tab {
2429 DCHECK(tab);
2430
2431 // Ignore changes while the tab stack view is visible (or while suspended).
2432 // The display will be refreshed when this view becomes active again.
2433 if (!self.visible || ![_model webUsageEnabled])
2434 return;
2435
2436 [self displayTab:tab isNewSelection:YES];
2437
kkhorimotoa44349c12017-04-12 23:02:122438 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132439 // Now that the new tab has been displayed, return to normal. Rather than
2440 // keep a reference to the previous tab, just turn off preview mode for all
2441 // tabs (since doing so is a no-op for the tabs that don't have it set).
2442 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272443 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132444 [tab.webController setOverlayPreviewMode:NO];
2445 }
2446 }
2447}
2448
Eugene Butf2d7fee2017-09-16 01:27:552449#pragma mark - External files
2450
2451- (void)removeExternalFilesImmediately:(BOOL)immediately
2452 completionHandler:(ProceduralBlock)completionHandler {
2453 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2454 DCHECK(!_isOffTheRecord);
2455 _externalFileRemover =
2456 std::make_unique<ExternalFileRemover>(self.browserState);
2457 // Delay the cleanup of the unreferenced files received from other apps
2458 // to not impact startup performance.
2459 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2460 _externalFileRemover->RemoveAfterDelay(
2461 base::TimeDelta::FromSeconds(delay),
2462 base::BindBlockArc(completionHandler ? completionHandler
2463 : ^{
2464 }));
2465}
2466
edchinf5150c682017-09-18 02:50:032467- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2468 return [self.tabStripCoordinator placeholderView];
2469}
2470
Sylvain Defresne41170aa2017-06-15 10:25:202471- (void)shutdown {
2472 DCHECK(!_isShutdown);
2473 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032474 [self.tabStripCoordinator stop];
2475 self.tabStripCoordinator = nil;
2476 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202477 _infoBarContainer = nil;
2478 _readingListMenuNotifier = nil;
2479 if (_bookmarkModel)
2480 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2481 [_model removeObserver:self];
2482 [[UpgradeCenter sharedInstance] unregisterClient:self];
2483 [[NSNotificationCenter defaultCenter] removeObserver:self];
2484 [_toolbarController setDelegate:nil];
2485 if (_voiceSearchController)
2486 _voiceSearchController->SetDelegate(nil);
2487 [_rateThisAppDialog setDelegate:nil];
2488 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462489 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202490}
2491
sdefresnee65fd872016-12-19 13:38:132492#pragma mark - SnapshotOverlayProvider methods
2493
2494- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2495 NSMutableArray* overlays = [NSMutableArray array];
2496 if (![_model webUsageEnabled]) {
2497 return overlays;
2498 }
2499 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2500 if (voiceSearchView) {
2501 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272502 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132503 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272504 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132505 [overlays addObject:voiceSearchOverlay];
2506 }
2507 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2508 if (infoBarView) {
2509 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272510 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132511 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272512 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132513 [overlays addObject:infoBarOverlay];
2514 }
2515 return overlays;
2516}
2517
2518#pragma mark -
2519
2520- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2521 if (IsIPadIdiom()) {
2522 // Not using overlays on iPad because the content is pushed down by
2523 // infobar and the transition between snapshot and fresh page can
2524 // cause both snapshot and real infobars to appear at the same time.
2525 return nil;
2526 }
2527 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302528 if (currentTab && tab == currentTab) {
2529 DCHECK(currentTab.webState);
2530 infobars::InfoBarManager* infoBarManager =
2531 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132532 if (infoBarManager->infobar_count() > 0) {
2533 DCHECK(_infoBarContainer);
2534 return _infoBarContainer->view();
2535 }
2536 }
2537 return nil;
2538}
2539
2540- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272541 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132542 // There is no UI representation for non-current tabs or there is
2543 // no _infoBarContainer instantiated yet.
2544 // Return offset outside of tab.
2545 return CGRectGetMaxY(self.view.frame);
2546 } else if (IsIPadIdiom()) {
2547 // The infobars on iPad are display at the top of a tab.
2548 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2549 } else {
2550 // The infobars on iPhone are displayed at the bottom of a tab.
2551 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2552 return CGRectGetMaxY(visibleFrame) -
2553 CGRectGetHeight(_infoBarContainer->view().frame);
2554 }
2555}
2556
2557- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2558 Tab* currentTab = [_model currentTab];
2559 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2560 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2561 return _voiceSearchBar;
2562 }
2563 return nil;
2564}
2565
2566- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2567 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2568 // There is no UI representation for non-current tabs or there is
2569 // no visible voice search. Return offset outside of tab.
2570 return CGRectGetMaxY(self.view.frame);
2571 } else {
2572 // The voice search bar on iPhone is displayed at the bottom of a tab.
2573 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2574 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2575 }
2576}
2577
2578- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272579 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132580 VoiceSearchProvider* provider =
2581 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2582 if (provider) {
2583 _voiceSearchController =
2584 provider->CreateVoiceSearchController(_browserState);
2585 _voiceSearchController->SetDelegate(_toolbarController);
2586 }
2587 }
2588}
2589
2590- (void)ensureVoiceSearchBarCreated {
2591 if (_voiceSearchBar)
2592 return;
2593
2594 CGFloat width = CGRectGetWidth([[self view] bounds]);
2595 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2596 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272597 _voiceSearchBar = ios::GetChromeBrowserProvider()
2598 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002599 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132600 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2601 [_voiceSearchBar setHidden:YES];
2602 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2603 UIViewAutoresizingFlexibleWidth];
2604 [self.view insertSubview:_voiceSearchBar
2605 belowSubview:_infoBarContainer->view()];
2606}
2607
2608- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2609 // Voice search bar exists and is shown/hidden.
2610 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272611 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132612 return;
2613
2614 // Voice search bar doesn't exist and thus is not visible.
2615 if (!_voiceSearchBar && !show)
2616 return;
2617
2618 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272619 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132620 else
stkhapuginc9eee7b2017-04-10 15:49:272621 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132622}
2623
2624- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2625 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2626 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2627 self.shouldShowVoiceSearchBar) {
2628 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2629 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272630 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132631 }
2632 id currentNativeController =
2633 [self nativeControllerForTab:self.tabModel.currentTab];
2634 Protocol* possibleOwnerProtocol =
2635 @protocol(LogoAnimationControllerOwnerOwner);
2636 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2637 [currentNativeController logoAnimationControllerOwner]) {
2638 // If the current native controller is showing a GLIF view (e.g. the NTP
2639 // when there is no doodle), use that GLIFControllerOwner.
2640 return [currentNativeController logoAnimationControllerOwner];
2641 }
2642 return nil;
2643}
2644
2645#pragma mark - PassKitDialogProvider methods
2646
2647- (void)presentPassKitDialog:(NSData*)data {
2648 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272649 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132650 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272651 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132652 if (error || !data) {
2653 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302654 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132655 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302656 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132657 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2658 // NULL, replace if with DCHECK).
2659 if (infoBarManager)
2660 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2661 }
2662 } else {
2663 PKAddPassesViewController* passKitViewController =
2664 [_dependencyFactory newPassKitViewControllerForPass:pass];
2665 if (passKitViewController) {
2666 [self presentViewController:passKitViewController
2667 animated:YES
2668 completion:^{
2669 }];
2670 }
2671 }
2672}
2673
2674- (UIStatusBarStyle)preferredStatusBarStyle {
2675 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2676 : UIStatusBarStyleDefault;
2677}
2678
2679#pragma mark - CRWWebStateDelegate methods.
2680
eugenebut75a06fa72017-01-09 17:09:552681- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512682 createNewWebStateForURL:(const GURL&)URL
2683 openerURL:(const GURL&)openerURL
2684 initiatedByUser:(BOOL)initiatedByUser {
2685 // Check if requested web state is a popup and block it if necessary.
2686 if (!initiatedByUser) {
2687 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2688 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102689 // It's possible for a page to inject a popup into a window created via
2690 // window.open before its initial load is committed. Rather than relying
2691 // on the last committed or pending NavigationItem's referrer policy, just
2692 // use ReferrerPolicyDefault.
2693 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2694 // policy once referrer policies are correctly recorded in
2695 // NavigationItems.
2696 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512697 helper->HandlePopup(URL, referrer);
2698 return nil;
2699 }
2700 }
2701
2702 // Requested web state should not be blocked from opening.
2703 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2704 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2705
2706 // Tabs open by DOM are always renderer initiated.
2707 web::NavigationManager::WebLoadParams params(GURL{});
2708 params.transition_type = ui::PAGE_TRANSITION_LINK;
2709 params.is_renderer_initiated = true;
2710 Tab* childTab = [[self tabModel]
2711 insertTabWithLoadParams:params
2712 opener:currentTab
2713 openedByDOM:YES
2714 atIndex:TabModelConstants::kTabPositionAutomatically
2715 inBackground:NO];
2716 return childTab.webState;
2717}
2718
eugenebutb46b2122017-03-14 02:43:262719- (void)closeWebState:(web::WebState*)webState {
2720 // Only allow a web page to close itself if it was opened by DOM, or if there
2721 // are no navigation items.
2722 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582723 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262724
2725 if (![self tabModel])
2726 return;
2727
2728 NSUInteger index = [[self tabModel] indexOfTab:tab];
2729 if (index != NSNotFound)
2730 [[self tabModel] closeTabAtIndex:index];
2731}
2732
eugenebut275f5892017-03-09 22:20:512733- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552734 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2735 switch (params.disposition) {
2736 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2737 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2738 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352739 insertTabWithURL:params.url
2740 referrer:params.referrer
2741 transition:params.transition
2742 opener:LegacyTabHelper::GetTabForWebState(webState)
2743 openedByDOM:NO
2744 atIndex:TabModelConstants::kTabPositionAutomatically
2745 inBackground:(params.disposition ==
2746 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552747 return tab.webState;
2748 }
2749 case WindowOpenDisposition::CURRENT_TAB: {
2750 web::NavigationManager::WebLoadParams loadParams(params.url);
2751 loadParams.referrer = params.referrer;
2752 loadParams.transition_type = params.transition;
2753 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2754 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2755 return webState;
2756 }
eugenebutd0984e82017-02-22 23:47:512757 case WindowOpenDisposition::NEW_POPUP: {
2758 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352759 insertTabWithURL:params.url
2760 referrer:params.referrer
2761 transition:params.transition
2762 opener:LegacyTabHelper::GetTabForWebState(webState)
2763 openedByDOM:YES
2764 atIndex:TabModelConstants::kTabPositionAutomatically
2765 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512766 return tab.webState;
2767 }
eugenebut75a06fa72017-01-09 17:09:552768 default:
2769 NOTIMPLEMENTED();
2770 return nullptr;
2771 };
2772}
2773
Mike Dougherty4e6b3a32017-08-23 18:49:212774- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132775 handleContextMenu:(const web::ContextMenuParams&)params {
2776 // Prevent context menu from displaying for a tab which is no longer the
2777 // current one.
2778 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212779 return;
sdefresnee65fd872016-12-19 13:38:132780 }
2781
2782 // No custom context menu if no valid url is available in |params|.
2783 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212784 return;
sdefresnee65fd872016-12-19 13:38:132785 }
2786
2787 DCHECK(_browserState);
2788 DCHECK([_model currentTab]);
2789
stkhapuginc9eee7b2017-04-10 15:49:272790 _contextMenuCoordinator =
2791 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2792 params:params];
sdefresnee65fd872016-12-19 13:38:132793
2794 NSString* title = nil;
2795 ProceduralBlock action = nil;
2796
stkhapuginc9eee7b2017-04-10 15:49:272797 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132798 GURL link = params.link_url;
2799 bool isLink = link.is_valid();
2800 GURL imageUrl = params.src_url;
2801 bool isImage = imageUrl.is_valid();
kkhorimotob110b262017-06-01 18:38:252802 const GURL& committedURL = [_model currentTab].lastCommittedURL;
sdefresnee65fd872016-12-19 13:38:132803
2804 if (isLink) {
2805 if (link.SchemeIs(url::kJavaScriptScheme)) {
2806 // Open
2807 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2808 action = ^{
2809 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2810 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2811 };
2812 [_contextMenuCoordinator addItemWithTitle:title action:action];
2813 }
2814
2815 if (web::UrlHasWebScheme(link)) {
kkhorimotob110b262017-06-01 18:38:252816 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132817
sdefresnee65fd872016-12-19 13:38:132818 // Open in New Tab.
2819 title = l10n_util::GetNSStringWithFixup(
2820 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2821 action = ^{
2822 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002823 // The "New Tab" item in the context menu opens a new tab in the current
2824 // browser state. |isOffTheRecord| indicates whether or not the current
2825 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132826 [weakSelf webPageOrderedOpen:link
2827 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002828 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132829 inBackground:YES
2830 appendTo:kCurrentTab];
2831 };
2832 [_contextMenuCoordinator addItemWithTitle:title action:action];
2833 if (!_isOffTheRecord) {
2834 // Open in Incognito Tab.
2835 title = l10n_util::GetNSStringWithFixup(
2836 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2837 action = ^{
2838 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2839 [weakSelf webPageOrderedOpen:link
2840 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132841 inIncognito:YES
2842 inBackground:NO
2843 appendTo:kCurrentTab];
2844 };
2845 [_contextMenuCoordinator addItemWithTitle:title action:action];
2846 }
olivierrobin51d4cf42017-01-17 13:32:352847 }
gambard65d69152017-03-23 17:44:222848 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352849 NSString* innerText = params.link_text;
2850 if ([innerText length] > 0) {
2851 // Add to reading list.
2852 title = l10n_util::GetNSStringWithFixup(
2853 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2854 action = ^{
2855 Record(ACTION_READ_LATER, isImage, isLink);
2856 [weakSelf addToReadingListURL:link title:innerText];
2857 };
2858 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532859 }
sdefresnee65fd872016-12-19 13:38:132860 }
2861 // Copy Link.
2862 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2863 action = ^{
2864 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282865 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132866 };
2867 [_contextMenuCoordinator addItemWithTitle:title action:action];
2868 }
2869 if (isImage) {
kkhorimotob110b262017-06-01 18:38:252870 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132871 // Save Image.
gambard98b4ddf2017-04-18 07:14:052872 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132873 action = ^{
2874 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2875 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2876 };
2877 [_contextMenuCoordinator addItemWithTitle:title action:action];
2878 // Open Image.
2879 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2880 action = ^{
2881 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2882 [weakSelf loadURL:imageUrl
2883 referrer:referrer
2884 transition:ui::PAGE_TRANSITION_LINK
2885 rendererInitiated:YES];
2886 };
2887 [_contextMenuCoordinator addItemWithTitle:title action:action];
2888 // Open Image In New Tab.
2889 title = l10n_util::GetNSStringWithFixup(
2890 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2891 action = ^{
2892 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2893 [weakSelf webPageOrderedOpen:imageUrl
2894 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132895 inBackground:true
2896 appendTo:kCurrentTab];
2897 };
2898 [_contextMenuCoordinator addItemWithTitle:title action:action];
2899
2900 TemplateURLService* service =
2901 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102902 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132903 if (defaultURL && !defaultURL->image_url().empty() &&
2904 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2905 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2906 defaultURL->short_name());
2907 action = ^{
2908 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2909 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2910 };
2911 [_contextMenuCoordinator addItemWithTitle:title action:action];
2912 }
2913 }
2914
2915 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:132916}
2917
eugenebutb739bdc2017-01-25 06:32:482918- (void)webState:(web::WebState*)webState
2919 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2920 // Display the action sheet with the arrow pointing at the top center of the
2921 // web contents.
sdefresne0452a9d2017-02-09 15:33:282922 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482923 UIView* view = webState->GetView();
2924 CGPoint dialogLocation =
2925 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282926 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032927 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172928 helper->PresentDialog(dialogLocation,
2929 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052930 handler(shouldContinue);
2931 }));
eugenebutb739bdc2017-01-25 06:32:482932}
2933
sdefresnee65fd872016-12-19 13:38:132934- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2935 (web::WebState*)webState {
2936 return _javaScriptDialogPresenter.get();
2937}
2938
eugenebut63232102017-01-19 16:19:402939- (void)webState:(web::WebState*)webState
2940 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2941 proposedCredential:(NSURLCredential*)proposedCredential
2942 completionHandler:(void (^)(NSString* username,
2943 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422944 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2945 if ([tab isPrerenderTab]) {
2946 [tab discardPrerender];
2947 if (handler) {
2948 handler(nil, nil);
2949 }
2950 return;
2951 }
2952
eugenebut63232102017-01-19 16:19:402953 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2954 proposedCredential:proposedCredential
2955 webState:webState
2956 completionHandler:handler];
2957}
2958
sdefresnee65fd872016-12-19 13:38:132959#pragma mark - FullScreenControllerDelegate methods
2960
2961- (CGFloat)headerOffset {
2962 if (IsIPadIdiom())
2963 return StatusBarHeight();
2964 return 0.0;
2965}
2966
stkhapugin952ecef2017-04-11 12:11:452967- (NSArray<HeaderDefinition*>*)headerViews {
2968 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132969 if (![self isViewLoaded])
2970 return results;
2971
2972 if (!IsIPadIdiom()) {
2973 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452974 [results addObject:[HeaderDefinition
2975 definitionWithView:[_toolbarController view]
2976 headerBehaviour:Hideable
2977 heightAdjustment:[ToolbarController
2978 toolbarDropShadowHeight]
2979 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132980 }
2981 } else {
edchinf5150c682017-09-18 02:50:032982 if (self.tabStripView) {
2983 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
2984 headerBehaviour:Hideable
2985 heightAdjustment:0.0
2986 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132987 }
2988 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452989 [results addObject:[HeaderDefinition
2990 definitionWithView:[_toolbarController view]
2991 headerBehaviour:Hideable
2992 heightAdjustment:[ToolbarController
2993 toolbarDropShadowHeight]
2994 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132995 }
2996 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452997 [results addObject:[HeaderDefinition
2998 definitionWithView:[_findBarController view]
2999 headerBehaviour:Overlap
3000 heightAdjustment:0.0
3001 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133002 }
3003 }
stkhapugin952ecef2017-04-11 12:11:453004 return [results copy];
sdefresnee65fd872016-12-19 13:38:133005}
3006
3007- (UIView*)footerView {
3008 return _voiceSearchBar;
3009}
3010
3011- (CGFloat)headerHeight {
3012 return [self headerHeightForTab:[_model currentTab]];
3013}
3014
3015- (CGFloat)headerHeightForTab:(Tab*)tab {
3016 id nativeController = [self nativeControllerForTab:tab];
3017 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3018 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3019 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3020 // On iPhone, don't add any header height for ToolbarOwner native
3021 // controllers when they're displaying their own toolbar.
3022 return 0;
3023 }
3024
stkhapugin952ecef2017-04-11 12:11:453025 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133026
3027 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453028 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133029 if (header.view && header.behaviour == Hideable) {
3030 height += CGRectGetHeight([header.view frame]) -
3031 header.heightAdjustement - header.inset;
3032 }
3033 }
3034
3035 return height - StatusBarHeight();
3036}
3037
3038- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193039 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133040}
3041
3042- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453043 NSArray<HeaderDefinition*>* headers = [self headerViews];
3044 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133045 return 0.0;
3046
3047 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3048 // API documentation.
3049 if ([[[self tabModel] currentTab] isPrerenderTab])
3050 return [self headerHeight];
3051
3052 UIView* topHeader = headers[0].view;
3053 return -(topHeader.frame.origin.y - [self headerOffset]);
3054}
3055
3056- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3057 UIView* footer = [self footerView];
3058 CGFloat headerHeight = [self headerHeight];
3059 if (!footer || headerHeight == 0)
3060 return 0.0;
3061
3062 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3063 CGFloat offset = headerOffset * footerHeight / headerHeight;
3064 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3065}
3066
3067- (void)fullScreenController:(FullScreenController*)controller
3068 headerAnimationCompleted:(BOOL)completed
3069 offset:(CGFloat)offset {
3070 if (completed)
justincohen04c27772016-12-21 20:16:593071 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133072}
3073
stkhapugin952ecef2017-04-11 12:11:453074- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133075 atOffset:(CGFloat)headerOffset {
3076 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453077 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133078 CGRect frame = [header.view frame];
3079 frame.origin.y = height - headerOffset - header.inset;
3080 [header.view setFrame:frame];
3081 if (header.behaviour != Overlap)
3082 height += CGRectGetHeight(frame);
3083 }
3084}
3085
3086- (void)fullScreenController:(FullScreenController*)fullScreenController
3087 drawHeaderViewFromOffset:(CGFloat)headerOffset
3088 animate:(BOOL)animate {
3089 if ([_sideSwipeController inSwipe])
3090 return;
3091
3092 CGRect footerFrame = CGRectZero;
3093 UIView* footer = nil;
3094 // Only animate the voice search bar if the tab is a voice search results tab.
3095 if ([_model currentTab].isVoiceSearchResultsTab) {
3096 footer = [self footerView];
3097 footerFrame = footer.frame;
3098 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3099 }
3100
stkhapugin952ecef2017-04-11 12:11:453101 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133102 void (^block)(void) = ^{
3103 [self setFramesForHeaders:headers atOffset:headerOffset];
3104 footer.frame = footerFrame;
3105 };
3106 void (^completion)(BOOL) = ^(BOOL finished) {
3107 [self fullScreenController:fullScreenController
3108 headerAnimationCompleted:finished
3109 offset:headerOffset];
3110 };
3111 if (animate) {
Sylvain Defresneed8c0db2017-08-31 16:29:523112 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133113 delay:0.0
3114 options:UIViewAnimationOptionBeginFromCurrentState
3115 animations:block
3116 completion:completion];
3117 } else {
3118 block();
3119 completion(YES);
3120 }
3121}
3122
3123- (void)fullScreenController:(FullScreenController*)fullScreenController
3124 drawHeaderViewFromOffset:(CGFloat)headerOffset
3125 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3126 changeTopContentPadding:(BOOL)changeTopContentPadding
3127 scrollingToOffset:(CGFloat)contentOffset {
3128 DCHECK(webViewProxy);
3129 if ([_sideSwipeController inSwipe])
3130 return;
3131
3132 CGRect footerFrame;
3133 UIView* footer = nil;
3134 // Only animate the voice search bar if the tab is a voice search results tab.
3135 if ([_model currentTab].isVoiceSearchResultsTab) {
3136 footer = [self footerView];
3137 footerFrame = footer.frame;
3138 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3139 }
3140
stkhapugin952ecef2017-04-11 12:11:453141 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133142 void (^block)(void) = ^{
3143 [self setFramesForHeaders:headers atOffset:headerOffset];
3144 footer.frame = footerFrame;
3145 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3146 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3147 if (changeTopContentPadding)
3148 webViewProxy.topContentPadding = contentOffset;
3149 };
3150 void (^completion)(BOOL) = ^(BOOL finished) {
3151 [self fullScreenController:fullScreenController
3152 headerAnimationCompleted:finished
3153 offset:headerOffset];
3154 };
3155
Sylvain Defresneed8c0db2017-08-31 16:29:523156 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133157 delay:0.0
3158 options:UIViewAnimationOptionBeginFromCurrentState
3159 animations:block
3160 completion:completion];
3161}
3162
3163#pragma mark - VoiceSearchBarOwner
3164
3165- (id<VoiceSearchBar>)voiceSearchBar {
3166 return _voiceSearchBar;
3167}
3168
3169#pragma mark - Install OverScrollActionController method.
3170- (void)setOverScrollActionControllerToStaticNativeContent:
3171 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343172 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133173 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173174 [[OverscrollActionsController alloc]
3175 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133176 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053177 OverscrollStyle style = _isOffTheRecord
3178 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3179 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133180 controller.style = style;
3181 nativeContent.overscrollActionsController = controller;
3182 }
3183}
3184
3185#pragma mark - OverscrollActionsControllerDelegate methods.
3186
3187- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053188 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133189 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053190 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383191 [self.dispatcher
3192 openNewTab:[OpenNewTabCommand
3193 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133194 break;
rohitrao922b7111c2017-01-03 14:31:053195 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243196 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133197 break;
liaoyuke563dc4a2017-03-17 18:36:293198 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:133199 if ([[[_model currentTab] webController] loadPhase] ==
3200 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:233201 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:133202 }
liaoyuke563dc4a2017-03-17 18:36:293203
3204 web::WebState* webState = [_model currentTab].webState;
3205 if (webState)
3206 // |check_for_repost| is true because the reload is explicitly initiated
3207 // by the user.
3208 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3209 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133210 break;
liaoyuke563dc4a2017-03-17 18:36:293211 }
rohitrao922b7111c2017-01-03 14:31:053212 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133213 NOTREACHED();
3214 break;
3215 }
3216}
3217
3218- (BOOL)shouldAllowOverscrollActions {
3219 return YES;
3220}
3221
3222- (UIView*)headerView {
3223 return [_toolbarController view];
3224}
3225
3226- (UIView*)toolbarSnapshotView {
3227 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
3228}
3229
3230- (CGFloat)overscrollActionsControllerHeaderInset:
3231 (OverscrollActionsController*)controller {
3232 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3233 return [self headerHeight];
3234 else
3235 return 0;
3236}
3237
3238- (CGFloat)overscrollHeaderHeight {
3239 return [self headerHeight] + StatusBarHeight();
3240}
3241
3242#pragma mark - TabSnapshottingDelegate methods.
3243
3244- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3245 CGRect pageContentArea = _contentArea.bounds;
3246 if ([_model webUsageEnabled])
3247 pageContentArea = tab.view.bounds;
3248 CGFloat headerHeight = [self headerHeightForTab:tab];
3249 id nativeController = [self nativeControllerForTab:tab];
3250 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3251 headerHeight += [nativeController toolbarHeight];
3252 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3253 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3254}
3255
3256#pragma mark - NewTabPageObserver methods.
3257
3258- (void)selectedPanelDidChange {
3259 [self updateToolbar];
3260}
3261
3262#pragma mark - CRWNativeContentProvider methods
3263
3264- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3265 withError:(NSError*)error
3266 isPost:(BOOL)isPost {
3267 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173268 [[ErrorPageContent alloc] initWithLoader:self
3269 browserState:self.browserState
3270 url:url
3271 error:error
3272 isPost:isPost
3273 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133274 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3275 return errorPageContent;
3276}
3277
3278- (BOOL)hasControllerForURL:(const GURL&)url {
3279 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453280 if (host == kChromeUIOfflineHost) {
3281 // Only allow offline URL that are fully specified.
3282 return reading_list::IsOfflineURLValid(
3283 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3284 }
sdefresnee65fd872016-12-19 13:38:133285
Justin Cohen8679e852017-08-14 16:35:253286 if (host == kChromeUIBookmarksHost) {
3287 // Only allow bookmark URL on iPad.
3288 return IsIPadIdiom();
3289 }
3290
3291 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133292}
3293
olivierrobind43eecb2017-01-27 20:35:263294- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3295 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133296 DCHECK(url.SchemeIs(kChromeUIScheme));
3297
3298 id<CRWNativeContent> nativeController = nil;
3299 std::string url_host = url.host();
Justin Cohen49715952017-08-22 14:12:193300 if (url_host == kChromeUINewTabHost ||
3301 (IsIPadIdiom() && url_host == kChromeUIBookmarksHost)) {
sdefresnee65fd872016-12-19 13:38:133302 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173303 [[NewTabPageController alloc] initWithUrl:url
3304 loader:self
3305 focuser:_toolbarController
3306 ntpObserver:self
3307 browserState:_browserState
3308 colorCache:_dominantColorCache
Gauthier Ambard8160349c2017-09-06 14:43:243309 toolbarDelegate:self
justincohenbc913632017-04-18 14:41:453310 tabModel:_model
justincohen75011c32017-04-28 16:31:393311 parentViewController:self
edchin3365c7d2017-09-01 22:20:373312 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:133313 pageController.swipeRecognizerProvider = self.sideSwipeController;
3314
3315 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233316 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133317
3318 if (IsIPadIdiom()) {
3319 // New Tab Page can have multiple panels. Each panel is addressable
3320 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3321 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3322 // the Bookmark Manager, etc.
3323 // The utility functions NewTabPage::IdentifierFromFragment() and
3324 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3325 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3326 // without changing the URL since the URL may be chrome://bookmarks/#123.
3327 // If the URL is chrome://newtab/, pre-select the panel based on the
3328 // #fragment.
3329 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233330 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133331 : NewTabPage::IdentifierFromFragment(url.ref());
3332 }
3333 [pageController selectPanel:panelType];
3334 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453335 } else if (url_host == kChromeUIOfflineHost &&
3336 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133337 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173338 [[OfflinePageNativeContent alloc] initWithLoader:self
3339 browserState:_browserState
3340 webState:webState
3341 URL:url];
sdefresnee65fd872016-12-19 13:38:133342 [self setOverScrollActionControllerToStaticNativeContent:
3343 staticNativeController];
3344 nativeController = staticNativeController;
3345 } else if (url_host == kChromeUIExternalFileHost) {
3346 // Return an instance of the |ExternalFileController| only if the file is
3347 // still in the sandbox.
3348 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3349 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173350 nativeController =
3351 [[ExternalFileController alloc] initWithURL:url
3352 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133353 }
peterlaurens44615d02017-05-23 20:23:093354 } else if (url_host == kChromeUICrashHost) {
3355 // There is no native controller for kChromeUICrashHost, it is instead
3356 // handled as any other renderer crash by the SadTabTabHelper.
3357 // nativeController must be set to nil to prevent defaulting to a
3358 // PageNotAvailableController.
3359 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133360 } else {
3361 DCHECK(![self hasControllerForURL:url]);
3362 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173363 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133364 }
3365 // If a native controller is vended before its tab is added to the tab model,
3366 // use the temporary key and add it under the new tab's tabId in the
3367 // TabModelObserver callback. This happens:
3368 // - when there is no current tab (occurs when vending the NTP controller for
3369 // the first tab that is opened),
3370 // - when the current tab's url doesn't match |url| (occurs when a native
3371 // controller is opened in a new tab)
3372 // - when the current tab's url matches |url| and there is already a native
3373 // controller of the appropriate type vended to it (occurs when a native
3374 // controller is opened in a new tab from a tab with a matching URL, e.g.
3375 // opening an NTP when an NTP is already displayed in the current tab).
3376 // For normal page loads, history navigations, tab restorations, and crash
3377 // recoveries, the tab will already exist in the tab model and the tabId can
3378 // be used as the native controller key.
3379 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3380 // that native controllers vended here always correspond to the current tab.
3381 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:253382 if (!currentTab || currentTab.lastCommittedURL != url ||
Eugene But56efc322017-08-11 14:03:443383 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133384 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443385 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133386 }
sdefresnee65fd872016-12-19 13:38:133387 return nativeController;
3388}
3389
3390- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443391 id nativeController = tab.webController.nativeController;
3392 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133393}
3394
3395#pragma mark - DialogPresenterDelegate methods
3396
3397- (void)dialogPresenter:(DialogPresenter*)presenter
3398 willShowDialogForWebState:(web::WebState*)webState {
3399 for (Tab* iteratedTab in self.tabModel) {
3400 if ([iteratedTab webState] == webState) {
3401 self.tabModel.currentTab = iteratedTab;
3402 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3403 break;
3404 }
3405 }
3406}
3407
3408#pragma mark - Context menu methods
3409
3410- (void)searchByImageAtURL:(const GURL&)url
3411 referrer:(const web::Referrer)referrer {
3412 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273413 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113414 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173415 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3416 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113417 DCHECK(data);
3418 dispatch_async(dispatch_get_main_queue(), ^{
3419 [weakSelf searchByImageData:data atURL:image_source_url];
3420 });
3421 };
3422 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133423 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3424 web::PolicyForNavigation(url, referrer));
3425}
3426
3427- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3428 NSData* imageData = data;
3429 UIImage* image = [UIImage imageWithData:imageData];
3430 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3431 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3432 // kSearchByImageMaxImageHeight).
3433 if (image &&
3434 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3435 (image.size.width > kSearchByImageMaxImageWidth ||
3436 image.size.height > kSearchByImageMaxImageHeight)) {
3437 CGSize newImageSize =
3438 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3439 image = [image gtm_imageByResizingToSize:newImageSize
3440 preserveAspectRatio:YES
3441 trimToFit:NO];
3442 imageData = UIImageJPEGRepresentation(image, 1.0);
3443 }
3444
3445 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3446 std::string byteString(bytes, [imageData length]);
3447
3448 TemplateURLService* templateUrlService =
3449 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103450 const TemplateURL* defaultURL =
3451 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133452 DCHECK(!defaultURL->image_url().empty());
3453 DCHECK(defaultURL->image_url_ref().IsValid(
3454 templateUrlService->search_terms_data()));
3455 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3456 search_args.image_url = imageURL;
3457 search_args.image_thumbnail_content = byteString;
3458
3459 // Generate the URL and populate |post_content| with the content type and
3460 // HTTP body for the request.
3461 TemplateURLRef::PostContent post_content;
3462 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3463 search_args, templateUrlService->search_terms_data(), &post_content));
3464 [self addSelectedTabWithURL:result
3465 postData:&post_content
3466 transition:ui::PAGE_TRANSITION_TYPED];
3467}
3468
3469- (void)saveImageAtURL:(const GURL&)url
3470 referrer:(const web::Referrer&)referrer {
3471 DCHECK(url.is_valid());
3472
gambard9efce7a2017-02-09 18:53:173473 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3474 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113475 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133476
gambardbbf85c42017-06-29 11:15:343477 if ([data length] == 0) {
3478 [self displayPrivacyErrorAlertOnMainQueue:
3479 l10n_util::GetNSString(
3480 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3481 return;
3482 }
3483
gambard9efce7a2017-02-09 18:53:173484 base::FilePath::StringType extension;
3485
3486 bool extensionSuccess =
3487 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3488 if (!extensionSuccess || extension.length() == 0) {
3489 extension = "png";
3490 }
3491
3492 NSString* fileExtension =
3493 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3494 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113495 };
3496 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133497 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3498 web::PolicyForNavigation(url, referrer));
3499}
3500
gambard9efce7a2017-02-09 18:53:173501- (void)managePermissionAndSaveImage:(NSData*)data
3502 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133503 switch ([PHPhotoLibrary authorizationStatus]) {
3504 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173505 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133506 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3507 // Call -saveImage again to check if chrome needs to display an error or
3508 // saves the image.
3509 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173510 [self managePermissionAndSaveImage:data
3511 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133512 }];
3513 break;
stkhapuginf58b10d02017-04-10 13:36:173514 }
sdefresnee65fd872016-12-19 13:38:133515
3516 // The application doesn't have permission to access photo and the user
3517 // cannot grant it.
3518 case PHAuthorizationStatusRestricted:
3519 [self displayPrivacyErrorAlertOnMainQueue:
3520 l10n_util::GetNSString(
3521 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3522 break;
3523
3524 // The application doesn't have permission to access photo and the user
3525 // can grant it.
3526 case PHAuthorizationStatusDenied:
3527 [self displayImageErrorAlertWithSettingsOnMainQueue];
3528 break;
3529
3530 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243531 default:
3532 __weak BrowserViewController* weakSelf = self;
3533 [self saveImage:data
3534 withFileExtension:fileExtension
3535 completion:^(BOOL success, NSError* error) {
3536 [weakSelf finishSavingImageWithError:error];
3537 }];
sdefresnee65fd872016-12-19 13:38:133538 break;
sdefresnee65fd872016-12-19 13:38:133539 }
3540}
3541
Sylvain Defresnefd3ecf22017-07-12 18:47:243542- (void)saveImage:(NSData*)data
3543 withFileExtension:(NSString*)fileExtension
3544 completion:(void (^)(BOOL, NSError*))completion {
3545 base::PostTaskWithTraits(
3546 FROM_HERE,
3547 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3548 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3549 base::BindBlockArc(^{
3550 base::ThreadRestrictions::AssertIOAllowed();
sdefresnee65fd872016-12-19 13:38:133551
Sylvain Defresnefd3ecf22017-07-12 18:47:243552 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3553 stringByAppendingString:fileExtension];
3554 NSURL* fileURL = [NSURL
3555 fileURLWithPath:[NSTemporaryDirectory()
3556 stringByAppendingPathComponent:fileName]];
3557 NSError* error = nil;
3558 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3559 if (error) {
3560 if (completion)
3561 completion(NO, error);
3562 return;
3563 }
sdefresnee65fd872016-12-19 13:38:133564
Sylvain Defresnefd3ecf22017-07-12 18:47:243565 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3566 [PHAssetChangeRequest
3567 creationRequestForAssetFromImageAtFileURL:fileURL];
3568 }
3569 completionHandler:^(BOOL success, NSError* error) {
3570 base::PostTaskWithTraits(
3571 FROM_HERE,
3572 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3573 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3574 base::BindBlockArc(^{
3575 base::ThreadRestrictions::AssertIOAllowed();
3576 if (completion)
3577 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133578
Sylvain Defresnefd3ecf22017-07-12 18:47:243579 // Cleanup the temporary file.
3580 NSError* deleteFileError = nil;
3581 [[NSFileManager defaultManager]
3582 removeItemAtURL:fileURL
3583 error:&deleteFileError];
3584 }));
3585 }];
3586 }));
sdefresnee65fd872016-12-19 13:38:133587}
3588
3589- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3590 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3591 BOOL canGoToSetting =
3592 [[UIApplication sharedApplication] canOpenURL:settingURL];
3593 if (canGoToSetting) {
3594 dispatch_async(dispatch_get_main_queue(), ^{
3595 [self displayImageErrorAlertWithSettings:settingURL];
3596 });
3597 } else {
3598 [self displayPrivacyErrorAlertOnMainQueue:
3599 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3600 }
3601}
3602
3603- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3604 // Dismiss current alert.
3605 [_alertCoordinator stop];
3606
3607 NSString* title =
3608 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3609 NSString* message = l10n_util::GetNSString(
3610 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3611
stkhapuginc9eee7b2017-04-10 15:49:273612 _alertCoordinator =
3613 [[AlertCoordinator alloc] initWithBaseViewController:self
3614 title:title
3615 message:message];
sdefresnee65fd872016-12-19 13:38:133616
3617 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3618 action:nil
3619 style:UIAlertActionStyleCancel];
3620
3621 [_alertCoordinator
3622 addItemWithTitle:l10n_util::GetNSString(
3623 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3624 action:^{
3625 OpenUrlWithCompletionHandler(settingURL, nil);
3626 }
3627 style:UIAlertActionStyleDefault];
3628
3629 [_alertCoordinator start];
3630}
3631
3632- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3633 dispatch_async(dispatch_get_main_queue(), ^{
3634 NSString* title =
3635 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3636 [self showErrorAlertWithStringTitle:title message:errorContent];
3637 });
3638}
3639
3640// This callback is triggered when the image is effectively saved onto the photo
3641// album, or if the save failed for some reason.
3642- (void)finishSavingImageWithError:(NSError*)error {
3643 // Was there an error?
3644 if (error) {
3645 // Saving photo failed even though user has granted access to Photos.
3646 // Display the error information from the NSError object for user.
3647 NSString* errorMessage = [NSString
3648 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3649 [error domain], [error code]];
3650 // This code may be execute outside of the main thread. Make sure to display
3651 // the error on the main thread.
3652 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3653 } else {
3654 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3655 // the photo application. The current behaviour is to create the photo there
3656 // but not providing any link to it is suboptimal. That's what Safari is
3657 // doing, and what the PM want, but it doesn't make it right.
3658 }
3659}
3660
sdefresnee65fd872016-12-19 13:38:133661#pragma mark - Showing popups
3662
sdefresnee65fd872016-12-19 13:38:133663- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133664 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3665
3666 ReadingListModel* readingModel =
3667 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013668 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3669 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133670
pinkerton07e27842017-03-02 15:29:023671 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033672 [self showSnackbar:l10n_util::GetNSString(
3673 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133674}
3675
3676#pragma mark - Keyboard commands management
3677
3678- (BOOL)shouldRegisterKeyboardCommands {
3679 if ([self presentedViewController])
3680 return NO;
3681
3682 if (_voiceSearchController && _voiceSearchController->IsVisible())
3683 return NO;
3684
3685 // If there is no first responder, try to make the webview the first
3686 // responder.
3687 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273688 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133689 }
3690
3691 return YES;
3692}
3693
3694- (KeyCommandsProvider*)keyCommandsProvider {
3695 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273696 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133697 }
stkhapuginc9eee7b2017-04-10 15:49:273698 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133699}
3700
3701#pragma mark - KeyCommandsPlumbing
3702
3703- (BOOL)isOffTheRecord {
3704 return _isOffTheRecord;
3705}
3706
3707- (NSUInteger)tabsCount {
3708 return [_model count];
3709}
3710
lpromero47ea8862017-01-13 17:51:063711- (BOOL)canGoBack {
3712 return [_model currentTab].canGoBack;
3713}
3714
3715- (BOOL)canGoForward {
3716 return [_model currentTab].canGoForward;
3717}
3718
sdefresnee65fd872016-12-19 13:38:133719- (void)focusTabAtIndex:(NSUInteger)index {
3720 if ([_model count] > index) {
3721 [_model setCurrentTab:[_model tabAtIndex:index]];
3722 }
3723}
3724
3725- (void)focusNextTab {
3726 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3727 NSInteger modelCount = [_model count];
3728 if (currentTabIndex < modelCount - 1) {
3729 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3730 [_model setCurrentTab:nextTab];
3731 } else {
3732 [_model setCurrentTab:[_model tabAtIndex:0]];
3733 }
3734}
3735
3736- (void)focusPreviousTab {
3737 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3738 if (currentTabIndex > 0) {
3739 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3740 [_model setCurrentTab:previousTab];
3741 } else {
3742 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3743 [_model setCurrentTab:lastTab];
3744 }
3745}
3746
3747- (void)reopenClosedTab {
3748 sessions::TabRestoreService* const tabRestoreService =
3749 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3750 if (!tabRestoreService || tabRestoreService->entries().empty())
3751 return;
3752
3753 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3754 tabRestoreService->entries().front();
3755 // Only handle the TAB type.
3756 if (entry->type != sessions::TabRestoreService::TAB)
3757 return;
3758
Mark Cogandfcdea72017-07-18 13:47:383759 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133760 TabRestoreServiceDelegateImplIOS* const delegate =
3761 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3762 _browserState);
3763 tabRestoreService->RestoreEntryById(delegate, entry->id,
3764 WindowOpenDisposition::CURRENT_TAB);
3765}
3766
3767- (void)focusOmnibox {
3768 [_toolbarController focusOmnibox];
3769}
3770
3771#pragma mark - UIResponder
3772
3773- (NSArray*)keyCommands {
3774 if (![self shouldRegisterKeyboardCommands]) {
3775 return nil;
3776 }
3777 return [self.keyCommandsProvider
3778 keyCommandsForConsumer:self
Mark Cogan6c58ea92017-07-06 13:08:243779 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133780 editingText:![self isFirstResponder]];
3781}
3782
3783#pragma mark -
3784
3785// Induce an intentional crash in the browser process.
3786- (void)induceBrowserCrash {
3787 CHECK(false);
3788 // Call another function, so that the above CHECK can't be tail-call
3789 // optimized. This ensures that this method's name will show up in the stack
3790 // for easier identification.
3791 CHECK(true);
3792}
3793
3794- (void)loadURL:(const GURL&)url
3795 referrer:(const web::Referrer&)referrer
3796 transition:(ui::PageTransition)transition
3797 rendererInitiated:(BOOL)rendererInitiated {
3798 [[OmniboxGeolocationController sharedInstance]
3799 locationBarDidSubmitURL:url
3800 transition:transition
3801 browserState:_browserState];
3802
3803 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3804 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3805 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3806 }
3807
3808 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243809 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133810 // restart.
3811 if (url.host() == kChromeUIBrowserCrashHost) {
3812 [self induceBrowserCrash];
3813 // In debug the app can continue working even after the CHECK. Adding a
3814 // return avoids the crash url to be added to the history.
3815 return;
3816 }
3817
Rohit Rao44f204302017-08-10 14:49:543818 PrerenderService* prerenderService =
3819 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3820 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593821 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543822 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593823 DCHECK(newWebState);
3824
sdefresnee65fd872016-12-19 13:38:133825 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593826 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133827 DCHECK(oldTab);
3828 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593829
kkhorimotod804c5732017-03-15 23:44:523830 bool canPruneItems =
3831 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593832
kkhorimotod804c5732017-03-15 23:44:523833 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523834 [newTab navigationManager]->CopyStateFromAndPrune(
3835 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593836
3837 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3838 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133839
3840 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3841 // BrowserViewController to detect that a pre-rendered tab is switched in,
3842 // and show the prerendering animation.
3843 newTab.isPrerenderTab = NO;
3844
sdefresne2f7781c2017-03-02 19:12:463845 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133846 return;
3847 }
3848 }
3849
3850 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543851 if (prerenderService) {
3852 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133853 }
3854
sdefresnee65fd872016-12-19 13:38:133855 // Some URLs are not allowed while in incognito. If we are in incognito and
3856 // load a disallowed URL, instead create a new tab not in the incognito state.
3857 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3858 [self webPageOrderedOpen:url
3859 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133860 inIncognito:NO
3861 inBackground:NO
3862 appendTo:kCurrentTab];
3863 return;
3864 }
3865
mrefaata84d5a02017-06-08 17:13:293866 // If this is a reload initiated from the omnibox.
3867 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3868 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3869 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3870 [[_model currentTab] navigationManager]->Reload(
3871 web::ReloadType::NORMAL, true /* check_for_repost */);
3872 return;
3873 }
3874
sdefresnee65fd872016-12-19 13:38:133875 web::NavigationManager::WebLoadParams params(urlToLoad);
3876 params.referrer = referrer;
3877 params.transition_type = transition;
3878 params.is_renderer_initiated = rendererInitiated;
3879 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233880 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133881}
3882
3883- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:543884 PrerenderService* prerenderService =
3885 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3886 if (prerenderService) {
3887 prerenderService->CancelPrerender();
3888 }
sdefresnee65fd872016-12-19 13:38:133889 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:183890 if ([self currentWebState])
3891 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:133892}
3893
3894- (web::WebState*)currentWebState {
3895 return [[_model currentTab] webState];
3896}
3897
sdefresnee65fd872016-12-19 13:38:133898// Load a new URL on a new page/tab.
3899- (void)webPageOrderedOpen:(const GURL&)URL
3900 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133901 inBackground:(BOOL)inBackground
3902 appendTo:(OpenPosition)appendTo {
3903 Tab* adjacentTab = nil;
3904 if (appendTo == kCurrentTab)
3905 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353906 [_model insertTabWithURL:URL
3907 referrer:referrer
3908 transition:ui::PAGE_TRANSITION_LINK
3909 opener:adjacentTab
3910 openedByDOM:NO
3911 atIndex:TabModelConstants::kTabPositionAutomatically
3912 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133913}
3914
3915- (void)webPageOrderedOpen:(const GURL&)url
3916 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133917 inIncognito:(BOOL)inIncognito
3918 inBackground:(BOOL)inBackground
3919 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003920 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:233921 // feature_engagement::Tracker based on |inIncognito|.
3922 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003923
sdefresnee65fd872016-12-19 13:38:133924 if (inIncognito == _isOffTheRecord) {
3925 [self webPageOrderedOpen:url
3926 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133927 inBackground:inBackground
3928 appendTo:appendTo];
3929 return;
3930 }
3931 // When sending an open command that switches modes, ensure the tab
3932 // ends up appended to the end of the model, not just next to what is
3933 // currently selected in the other mode. This is done with the |append|
3934 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273935 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133936 initWithURL:url
3937 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133938 inIncognito:inIncognito
3939 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273940 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:153941 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:133942}
3943
3944- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:053945 WebStateList* webStateList = [_model webStateList];
3946 webStateList->ReplaceWebStateAt(
3947 webStateList->active_index(),
3948 session_util::CreateWebStateWithNavigationEntries(
3949 [_model browserState], sessionTab->current_navigation_index,
3950 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:133951}
3952
3953- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433954 DCHECK(javascript);
3955 javascript = [javascript stringByRemovingPercentEncoding];
3956 web::WebState* webState = [[_model currentTab] webState];
3957 if (webState) {
3958 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3959 }
sdefresnee65fd872016-12-19 13:38:133960}
3961
3962#pragma mark - WebToolbarDelegate methods
3963
3964- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3965 if (_locationBarHasFocus)
3966 return; // TODO(crbug.com/244366): This should not be necessary.
3967 _locationBarHasFocus = YES;
3968 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:523969 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:133970 object:nil];
3971 [_sideSwipeController setEnabled:NO];
3972 if ([[_model currentTab].webController wantsKeyboardShield]) {
3973 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3974 [_typingShield setAlpha:0.0];
3975 [_typingShield setHidden:NO];
3976 [UIView animateWithDuration:0.3
3977 animations:^{
3978 [_typingShield setAlpha:1.0];
3979 }];
3980 }
3981 [[OmniboxGeolocationController sharedInstance]
3982 locationBarDidBecomeFirstResponder:_browserState];
3983}
3984
3985- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3986 if (!_locationBarHasFocus)
3987 return; // TODO(crbug.com/244366): This should not be necessary.
3988 _locationBarHasFocus = NO;
3989 [_sideSwipeController setEnabled:YES];
3990 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:523991 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:133992 object:nil];
3993 [UIView animateWithDuration:0.3
3994 animations:^{
3995 [_typingShield setAlpha:0.0];
3996 }
3997 completion:^(BOOL finished) {
3998 // This can happen if one quickly resigns the omnibox and then taps
3999 // on the omnibox again during this animation. If the animation is
4000 // interrupted and the toolbar controller is first responder, it's safe
4001 // to assume the |_typingShield| shouldn't be hidden here.
4002 if (!finished && [_toolbarController isOmniboxFirstResponder])
4003 return;
4004 [_typingShield setHidden:YES];
4005 }];
4006 [[OmniboxGeolocationController sharedInstance]
4007 locationBarDidResignFirstResponder:_browserState];
4008
4009 // If a load was cancelled by an omnibox edit, but nothing is loading when
4010 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4011 if (_locationBarEditCancelledLoad) {
4012 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294013
4014 web::WebState* webState = [_model currentTab].webState;
4015 if (!_toolbarModelIOS->IsLoading() && webState)
4016 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4017 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134018 }
4019}
4020
4021- (IBAction)locationBarBeganEdit:(id)sender {
4022 // On handsets, if a page is currently loading it should be stopped.
4023 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354024 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134025 _locationBarEditCancelledLoad = YES;
4026 }
4027}
4028
sdefresnee65fd872016-12-19 13:38:134029- (ToolbarModelIOS*)toolbarModelIOS {
4030 return _toolbarModelIOS.get();
4031}
4032
sdefresnee65fd872016-12-19 13:38:134033- (void)willUpdateToolbarSnapshot {
4034 [[_model currentTab].overscrollActionsController clear];
4035}
4036
4037- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4038 CGRect frame = [_contentArea frame];
4039 if (!fullScreen) {
4040 // Changing the origin here is unnecessary, it's set in page_animation_util.
4041 frame.size.height -= [self headerHeight];
4042 }
4043
4044 CGFloat shortAxis = frame.size.width;
4045 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524046 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134047 CGFloat aspectRatio = frame.size.height / frame.size.width;
4048 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4049 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4050 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4051 CGRect cardFrame = {frame.origin, cardSize};
4052
4053 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174054 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134055 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4056 : CardCloseButtonSide::LEADING;
4057 [_contentArea addSubview:card];
4058 return card;
4059}
4060
Mark Cogan6ebbde02017-07-07 12:50:134061#pragma mark - BrowserCommands
4062
4063- (void)goBack {
4064 [[_model currentTab] goBack];
4065}
4066
4067- (void)goForward {
4068 [[_model currentTab] goForward];
4069}
4070
Mark Coganb9aac6432017-07-07 13:26:354071- (void)stopLoading {
4072 [_model currentTab].webState->Stop();
4073}
4074
4075- (void)reload {
4076 web::WebState* webState = [_model currentTab].webState;
4077 if (webState) {
4078 // |check_for_repost| is true because the reload is explicitly initiated
4079 // by the user.
4080 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4081 true /* check_for_repost */);
4082 }
4083}
4084
Mark Cogan8e791022017-07-10 09:55:354085- (void)bookmarkPage {
4086 [self initializeBookmarkInteractionController];
4087 [_bookmarkInteractionController
4088 presentBookmarkForTab:[_model currentTab]
4089 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
4090 inView:[_toolbarController bookmarkButtonView]
4091 originRect:[_toolbarController bookmarkButtonAnchorRect]];
4092}
4093
Mark Cogan6acee7f2017-07-11 09:01:404094- (void)showToolsMenu {
4095 DCHECK(_browserState);
4096 DCHECK(self.visible || self.dismissingModal);
4097
4098 // Record the time this menu was requested; to be stored in the configuration
4099 // object.
4100 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4101
4102 // Dismiss the omnibox (if open).
4103 [_toolbarController cancelOmniboxEdit];
4104 // Dismiss the soft keyboard (if open).
4105 [[_model currentTab].webController dismissKeyboard];
4106 // Dismiss Find in Page focus.
4107 [self updateFindBar:NO shouldFocus:NO];
4108
4109 ToolsMenuConfiguration* configuration =
4110 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
4111 configuration.requestStartTime =
4112 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4113 if ([_model count] == 0)
4114 [configuration setNoOpenedTabs:YES];
4115
4116 if (_isOffTheRecord)
4117 [configuration setInIncognito:YES];
4118
4119 if (!_readingListMenuNotifier) {
4120 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4121 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4122 _browserState)];
4123 }
Cooper Knaake4f495cf2017-07-27 23:30:034124
4125 feature_engagement::Tracker* engagementTracker =
4126 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4127 if (engagementTracker->ShouldTriggerHelpUI(
4128 feature_engagement::kIPHBadgedReadingListFeature)) {
4129 [configuration setShowReadingListNewBadge:YES];
4130 [configuration setEngagementTracker:engagementTracker];
4131 }
Mark Cogan6acee7f2017-07-11 09:01:404132 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4133
4134 [configuration setUserAgentType:self.userAgentType];
4135
Helen Yang9175bd52017-08-12 00:28:404136 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4137 [configuration setHighlightNewIncognitoTabCell:YES];
4138 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4139 }
4140
4141 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4142 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4143 }
4144
Mark Cogan6acee7f2017-07-11 09:01:404145 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
4146
4147 ToolsPopupController* toolsPopupController =
4148 [_toolbarController toolsPopupController];
4149 if ([_model currentTab]) {
4150 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4151 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4152 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404153 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4154
4155 if (!IsIPadIdiom())
4156 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4157 }
4158}
4159
Mark Cogandfcdea72017-07-18 13:47:384160- (void)openNewTab:(OpenNewTabCommand*)command {
4161 if (self.isOffTheRecord != command.incognito) {
4162 // Not for this browser state, send it on its way.
4163 [self.dispatcher switchModesAndOpenNewTab:command];
4164 return;
4165 }
4166
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004167 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234168 // events to the feature_engagement::Tracker based on |command.userInitiated|
4169 // and |command.incognito|.
4170 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004171
Mark Cogandfcdea72017-07-18 13:47:384172 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4173 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494174 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4175 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384176 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494177 if (oldForegroundTabWasAddedCompletionBlock) {
4178 oldForegroundTabWasAddedCompletionBlock();
4179 }
Mark Cogandfcdea72017-07-18 13:47:384180 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4181 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4182 if (offTheRecord) {
4183 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4184 timeDelta);
4185 } else {
4186 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4187 }
4188 };
4189
4190 [self setLastTapPoint:command];
4191 DCHECK(self.visible || self.dismissingModal);
4192 Tab* currentTab = [_model currentTab];
4193 if (currentTab) {
4194 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4195 }
4196 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4197 transition:ui::PAGE_TRANSITION_TYPED];
4198}
4199
Mark Cogan123895002017-07-20 12:54:064200- (void)printTab {
4201 Tab* currentTab = [_model currentTab];
4202 // The UI should prevent users from printing non-printable pages. However, a
4203 // redirection to an un-printable page can happen before it is reflected in
4204 // the UI.
4205 if (![currentTab viewForPrinting]) {
4206 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
4207 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
4208 return;
4209 }
4210 DCHECK(_browserState);
4211 if (!_printController) {
4212 _printController = [[PrintController alloc]
4213 initWithContextGetter:_browserState->GetRequestContext()];
4214 }
4215 [_printController printView:[currentTab viewForPrinting]
4216 withTitle:[currentTab title]
4217 viewController:self];
4218}
4219
Mark Coganfa25b052017-07-20 17:31:034220- (void)addToReadingList:(ReadingListAddCommand*)command {
4221 [self addToReadingListURL:[command URL] title:[command title]];
4222}
4223
sczs3a8c8602017-08-01 20:14:084224- (void)showReadingList {
4225 _readingListCoordinator = [[ReadingListCoordinator alloc]
4226 initWithBaseViewController:self
4227 browserState:self.browserState
4228 loader:self];
4229
4230 [_readingListCoordinator start];
4231}
4232
Jean-François Geyelinedef9552017-08-07 09:56:564233- (void)preloadVoiceSearch {
4234 // Preload VoiceSearchController and views and view controllers needed
4235 // for voice search.
4236 [self ensureVoiceSearchControllerCreated];
4237 _voiceSearchController->PrepareToAppear();
4238}
4239
edchinc5720722017-08-14 22:06:314240#if !defined(NDEBUG)
4241- (void)viewSource {
4242 Tab* tab = [_model currentTab];
4243 DCHECK(tab);
4244 CRWWebController* webController = tab.webController;
4245 NSString* script = @"document.documentElement.outerHTML;";
4246 __weak Tab* weakTab = tab;
4247 __weak BrowserViewController* weakSelf = self;
4248 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4249 Tab* strongTab = weakTab;
4250 if (!strongTab)
4251 return;
4252 if (![result isKindOfClass:[NSString class]])
4253 result = @"Not an HTML page";
4254 std::string base64HTML;
4255 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4256 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
4257 web::Referrer referrer([strongTab lastCommittedURL],
4258 web::ReferrerPolicyDefault);
4259
4260 [[weakSelf tabModel]
4261 insertTabWithURL:URL
4262 referrer:referrer
4263 transition:ui::PAGE_TRANSITION_LINK
4264 opener:strongTab
4265 openedByDOM:YES
4266 atIndex:TabModelConstants::kTabPositionAutomatically
4267 inBackground:NO];
4268 };
4269 [webController executeJavaScript:script
4270 completionHandler:completionHandlerBlock];
4271}
4272#endif // !defined(NDEBUG)
4273
edchin2134c042017-08-18 13:57:354274// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4275- (void)showRateThisAppDialog {
4276 DCHECK(!_rateThisAppDialog);
4277
4278 // Store the current timestamp whenever this dialog is shown.
4279 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4280 base::Time::Now().ToInternalValue());
4281
4282 // Some versions of iOS7 do not support linking directly to the "Ratings and
4283 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4284 // links to the main appstore page for the Chrome app.
4285 NSURL* storeURL =
4286 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4287 @"MZStore.woa/wa/"
4288 @"viewContentsUserReviews?type=Purple+Software&id="
4289 @"535886823&pt=9008&ct=rating")];
4290
4291 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4292 [self clearPresentedStateWithCompletion:nil];
4293
4294 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4295 [_rateThisAppDialog setAppStoreURL:storeURL];
4296 [_rateThisAppDialog setDelegate:self];
4297 [_rateThisAppDialog show];
4298}
4299
Gregory Chatzinoff3f40c1542017-08-30 07:50:044300- (void)showFindInPage {
4301 if (!self.canShowFindBar)
4302 return;
4303
4304 if (!_findBarController) {
4305 _findBarController =
4306 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4307 _findBarController.dispatcher = self.dispatcher;
4308 }
4309
4310 Tab* tab = [_model currentTab];
4311 DCHECK(tab);
4312 auto* helper = FindTabHelper::FromWebState(tab.webState);
4313 DCHECK(!helper->IsFindUIActive());
4314 helper->SetFindUIActive(true);
4315 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4316}
4317
4318- (void)closeFindInPage {
4319 __weak BrowserViewController* weakSelf = self;
4320 Tab* currentTab = [_model currentTab];
4321 if (currentTab) {
4322 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4323 [weakSelf updateFindBar:NO shouldFocus:NO];
4324 });
4325 }
4326}
4327
4328- (void)searchFindInPage {
4329 DCHECK([_model currentTab]);
4330 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4331 __weak BrowserViewController* weakSelf = self;
4332 helper->StartFinding(
4333 [_findBarController searchTerm], ^(FindInPageModel* model) {
4334 BrowserViewController* strongSelf = weakSelf;
4335 if (!strongSelf) {
4336 return;
4337 }
4338 [strongSelf->_findBarController updateResultsCount:model];
4339 });
4340
4341 if (!_isOffTheRecord)
4342 helper->PersistSearchTerm();
4343}
4344
4345- (void)findNextStringInPage {
4346 Tab* currentTab = [_model currentTab];
4347 DCHECK(currentTab);
4348 // TODO(crbug.com/603524): Reshow find bar if necessary.
4349 FindTabHelper::FromWebState(currentTab.webState)
4350 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4351 [_findBarController updateResultsCount:model];
4352 });
4353}
4354
4355- (void)findPreviousStringInPage {
4356 Tab* currentTab = [_model currentTab];
4357 DCHECK(currentTab);
4358 // TODO(crbug.com/603524): Reshow find bar if necessary.
4359 FindTabHelper::FromWebState(currentTab.webState)
4360 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4361 [_findBarController updateResultsCount:model];
4362 });
4363}
4364
edchinf84b2502017-08-31 21:30:454365- (void)showHelpPage {
4366 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4367 [self webPageOrderedOpen:helpUrl
4368 referrer:web::Referrer()
4369 inBackground:NO
4370 appendTo:kCurrentTab];
4371}
4372
edchinb59b5602017-09-01 15:00:204373- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104374 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204375 [self showAllBookmarks];
4376 } else {
4377 [self initializeBookmarkInteractionController];
4378 [_bookmarkInteractionController presentBookmarks];
4379 }
4380}
4381
edchin8ee0807d2017-09-01 23:52:474382- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104383 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474384 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4385 } else {
4386 if (!self.recentTabsCoordinator) {
4387 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4388 initWithBaseViewController:self];
4389 self.recentTabsCoordinator.loader = self;
4390 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4391 self.recentTabsCoordinator.browserState = _browserState;
4392 }
4393 [self.recentTabsCoordinator start];
4394 }
4395}
4396
Mark Cogan6de7e9a2017-09-06 12:57:214397- (void)requestDesktopSite {
4398 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4399}
4400
4401- (void)requestMobileSite {
4402 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4403}
4404
sdefresnee65fd872016-12-19 13:38:134405#pragma mark - Command Handling
4406
sdefresnee65fd872016-12-19 13:38:134407- (void)closeCurrentTab {
4408 Tab* currentTab = [_model currentTab];
4409 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4410 if (tabIndex == NSNotFound)
4411 return;
4412
jif7fed8122017-02-08 13:15:254413 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4414 // iPad.
sdefresnee65fd872016-12-19 13:38:134415 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4416 exitingPage.image =
4417 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4418
4419 // Close the actual tab, and add its image as a subview.
4420 [_model closeTabAtIndex:tabIndex];
4421
4422 // Do not animate close in iPad.
4423 if (!IsIPadIdiom()) {
4424 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524425 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134426 exitingPage, 0, YES, IsPortrait(), ^{
4427 [exitingPage removeFromSuperview];
4428 });
4429 }
4430}
4431
sdefresnee65fd872016-12-19 13:38:134432- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
Rohit Rao01e0e002017-08-14 20:49:434433 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134434 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4435 [_bookmarkInteractionController dismissSnackbar];
4436 [_toolbarController cancelOmniboxEdit];
4437 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274438 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474439 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134440 if (_voiceSearchController)
4441 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364442
4443 Tab* currentTab = [_model currentTab];
4444 [currentTab dismissModals];
4445
rohitrao005a6432017-03-16 20:52:424446 if (currentTab) {
4447 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4448 if (findHelper) {
4449 findHelper->StopFinding(^{
4450 [self updateFindBar:NO shouldFocus:NO];
4451 });
4452 }
4453 }
rohitraob2bf3cb2017-02-10 14:10:364454
sdefresnee65fd872016-12-19 13:38:134455 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134456 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274457 _printController = nil;
jif7fed8122017-02-08 13:15:254458 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134459 [_contextMenuCoordinator stop];
4460 [self dismissRateThisAppDialog];
4461
4462 if (self.presentedViewController) {
4463 // Dismisses any other modal controllers that may be present, e.g. Recent
4464 // Tabs.
4465 // Note that currently, some controllers like the bookmark ones were already
4466 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4467 // but are still reported as the presentedViewController. The result is that
4468 // this will call -dismissViewControllerAnimated:completion: a second time
4469 // on it. It is not per se an issue, as it is a no-op. The problem is that
4470 // in such a case, the completion block is not called.
4471 // To ensure the completion is called, nil is passed here, and the
4472 // completion is called below.
4473 [self dismissViewControllerAnimated:NO completion:nil];
4474 // Dismissed controllers will be so after a delay. Queue the completion
4475 // callback after that.
4476 if (completion) {
4477 dispatch_after(
4478 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4479 dispatch_get_main_queue(), ^{
4480 completion();
4481 });
4482 }
4483 } else if (completion) {
4484 // If no view controllers are presented, we should be ok with dispatching
4485 // the completion block directly.
4486 dispatch_async(dispatch_get_main_queue(), completion);
4487 }
4488}
4489
sdefresnee65fd872016-12-19 13:38:134490#pragma mark - Find Bar
4491
4492- (void)hideFindBarWithAnimation:(BOOL)animate {
4493 [_findBarController hideFindBarView:animate];
4494}
4495
4496- (void)showFindBarWithAnimation:(BOOL)animate
4497 selectText:(BOOL)selectText
4498 shouldFocus:(BOOL)shouldFocus {
4499 DCHECK(_findBarController);
4500 Tab* tab = [_model currentTab];
4501 DCHECK(tab);
4502 CRWWebController* webController = tab.webController;
4503
4504 CGRect referenceFrame = CGRectZero;
4505 if (IsIPadIdiom()) {
4506 referenceFrame = webController.visibleFrame;
4507 referenceFrame.origin.y -= kIPadFindBarOverlap;
4508 } else {
4509 referenceFrame = _contentArea.frame;
4510 }
4511
4512 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4513 [_findBarController addFindBarView:animate
4514 intoView:self.view
4515 withFrame:referenceFrame
4516 alignWithFrame:omniboxFrame
4517 selectText:selectText];
4518 [self updateFindBar:YES shouldFocus:shouldFocus];
4519}
4520
sdefresnee65fd872016-12-19 13:38:134521- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324522 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4523 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4524 // in a crash.
4525 if (![_model currentTab]) {
4526 return;
4527 }
rohitrao005a6432017-03-16 20:52:424528 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4529 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134530 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424531 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134532 }
4533
4534 [self setFramesForHeaders:[self headerViews]
4535 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424536 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134537 initialUpdate:initialUpdate
4538 focusTextfield:shouldFocus];
4539 } else {
4540 [self hideFindBarWithAnimation:YES];
4541 }
4542}
4543
4544- (void)showAllBookmarks {
4545 DCHECK(self.visible || self.dismissingModal);
4546 GURL URL(kChromeUIBookmarksURL);
4547 Tab* tab = [_model currentTab];
4548 web::NavigationManager::WebLoadParams params(URL);
4549 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234550 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134551}
4552
Gauthier Ambardf520c022017-08-29 07:42:234553- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134554 DCHECK(self.visible || self.dismissingModal);
4555 GURL url(kChromeUINewTabURL);
4556 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4557 if (fragment != "") {
4558 GURL::Replacements replacement;
4559 replacement.SetRefStr(fragment);
4560 url = url.ReplaceComponents(replacement);
4561 }
4562 Tab* tab = [_model currentTab];
4563 web::NavigationManager::WebLoadParams params(url);
4564 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234565 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134566}
4567
sdefresnee65fd872016-12-19 13:38:134568- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274569 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134570 base::RecordAction(base::UserMetricsAction(
4571 "IOSRateThisAppDialogDismissedProgramatically"));
4572 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274573 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134574 }
4575}
4576
Jean-François Geyelin5d2e184c2017-07-28 19:48:004577- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4578 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134579 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124580 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134581 _startVoiceSearchAfterNewTabAnimation = YES;
4582 return;
4583 }
4584
4585 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4586 // dismiss the keyboard.
4587 [self closeFindInPage];
4588 [[_model currentTab].webController dismissKeyboard];
4589
4590 // Ensure that voice search objects are created.
4591 [self ensureVoiceSearchControllerCreated];
4592 [self ensureVoiceSearchBarCreated];
4593
4594 // Present voice search.
4595 [_voiceSearchBar prepareToPresentVoiceSearch];
4596 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4597 [_toolbarController cancelOmniboxEdit];
4598}
4599
4600#pragma mark - ToolbarOwner
4601
4602- (ToolbarController*)relinquishedToolbarController {
4603 if (_isToolbarControllerRelinquished)
4604 return nil;
4605
4606 ToolbarController* relinquishedToolbarController = nil;
4607 if ([_toolbarController view].hidden) {
4608 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:254609 if (currentTab && UrlHasChromeScheme(currentTab.lastCommittedURL)) {
sdefresnee65fd872016-12-19 13:38:134610 // Use the native content controller's toolbar when the BVC's is hidden.
4611 id nativeController = [self nativeControllerForTab:currentTab];
4612 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4613 relinquishedToolbarController =
4614 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274615 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134616 }
4617 }
4618 } else {
stkhapuginc9eee7b2017-04-10 15:49:274619 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134620 }
4621 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4622 return relinquishedToolbarController;
4623}
4624
4625- (void)reparentToolbarController {
4626 if (_isToolbarControllerRelinquished) {
4627 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4628 // A native content controller's toolbar has been relinquished.
4629 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274630 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134631 } else if ([_findBarController isFindInPageShown]) {
4632 [self.view insertSubview:[_toolbarController view]
4633 belowSubview:[_findBarController view]];
4634 } else {
4635 [self.view addSubview:[_toolbarController view]];
4636 }
sdefresnee65fd872016-12-19 13:38:134637 _isToolbarControllerRelinquished = NO;
4638 }
4639}
4640
4641#pragma mark - TabModelObserver methods
4642
4643// Observer method, tab inserted.
4644- (void)tabModel:(TabModel*)model
4645 didInsertTab:(Tab*)tab
4646 atIndex:(NSUInteger)modelIndex
4647 inForeground:(BOOL)fg {
4648 DCHECK(tab);
4649 [self installDelegatesForTab:tab];
4650
4651 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194652 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134653 }
4654}
4655
4656// Observer method, active tab changed.
4657- (void)tabModel:(TabModel*)model
4658 didChangeActiveTab:(Tab*)newTab
4659 previousTab:(Tab*)previousTab
4660 atIndex:(NSUInteger)index {
4661 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4662 // Currently this observer method is always called with a non-nil |newTab|,
4663 // but that may change in the future. Remove this DCHECK when it does.
4664 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274665 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304666 DCHECK(newTab.webState);
4667 infobars::InfoBarManager* infoBarManager =
4668 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134669 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4670 }
4671 [self updateVoiceSearchBarVisibilityAnimated:NO];
4672
Mohamad Ahmadi7d09ec32017-07-11 22:32:194673 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134674
sczs6ae47ad2017-09-06 17:26:534675 // Update the Sad Tab coordinator webstate so it matches the current tab
4676 // webstate.
4677 _sadTabCoordinator.webState = newTab.webState;
4678
sdefresnee65fd872016-12-19 13:38:134679 [self tabSelected:newTab];
4680 DCHECK_EQ(newTab, [model currentTab]);
4681 [self installDelegatesForTab:newTab];
4682}
4683
4684// Observer method, tab changed.
4685- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4686 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4687 if (tab == [_model currentTab]) {
4688 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134689 }
4690}
4691
sdefresne49cf2862017-03-15 13:46:144692// Observer method, tab replaced.
4693- (void)tabModel:(TabModel*)model
4694 didReplaceTab:(Tab*)oldTab
4695 withTab:(Tab*)newTab
4696 atIndex:(NSUInteger)index {
4697 [self uninstallDelegatesForTab:oldTab];
4698 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264699
michaeldo79909fb2017-05-09 23:42:504700 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304701 infobars::InfoBarManager* infoBarManager = nullptr;
4702 if (newTab) {
4703 DCHECK(newTab.webState);
4704 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4705 }
michaeldo79909fb2017-05-09 23:42:504706 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4707 }
4708
kkhorimotofa0844cc2017-03-20 17:01:264709 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4710 if (self.active && model.currentTab == newTab)
4711 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464712
4713 if (newTab)
4714 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144715}
4716
sdefresnee65fd872016-12-19 13:38:134717// A tab has been removed, remove its views from display if necessary.
4718- (void)tabModel:(TabModel*)model
4719 didRemoveTab:(Tab*)tab
4720 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144721 [self uninstallDelegatesForTab:tab];
4722
kkhorimoto496fdd72017-06-12 19:56:314723 // Cancel dialogs for |tab|'s WebState.
4724 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4725
sdefresnee65fd872016-12-19 13:38:134726 // Ignore changes while the tab stack view is visible (or while suspended).
4727 // The display will be refreshed when this view becomes active again.
4728 if (!self.visible || !model.webUsageEnabled)
4729 return;
4730
4731 // Remove the find bar for now.
4732 [self hideFindBarWithAnimation:NO];
4733}
4734
4735- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4736 if (tab == [model currentTab]) {
4737 [_contentArea displayContentView:nil];
4738 [_toolbarController selectedTabChanged];
4739 }
4740
Mohamad Ahmadi7d09ec32017-07-11 22:32:194741 [_paymentRequestManager stopTrackingWebState:tab.webState];
4742
sdefresnee65fd872016-12-19 13:38:134743 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4744 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194745 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134746 }
4747}
4748
4749// Called when the number of tabs changes. Update the toolbar accordingly.
4750- (void)tabModelDidChangeTabCount:(TabModel*)model {
4751 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134752 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134753}
4754
4755#pragma mark - Upgrade Detection
4756
4757- (void)showUpgrade:(UpgradeCenter*)center {
4758 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274759 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134760 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304761 DCHECK(tab.webState);
4762 infobars::InfoBarManager* infoBarManager =
4763 InfoBarManagerImpl::FromWebState(tab.webState);
4764 DCHECK(infoBarManager);
4765 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134766 }
4767}
4768
sdefresnee65fd872016-12-19 13:38:134769
4770#pragma mark - InfoBarControllerDelegate
4771
4772- (void)infoBarContainerStateChanged:(bool)isAnimating {
4773 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4774 DCHECK(infoBarContainerView);
4775 CGRect containerFrame = infoBarContainerView.frame;
4776 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4777 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4778 containerFrame.size.height = height;
4779 BOOL isViewVisible = self.visible;
4780 [UIView animateWithDuration:0.1
4781 animations:^{
4782 [infoBarContainerView setFrame:containerFrame];
4783 }
4784 completion:^(BOOL finished) {
4785 if (!isViewVisible)
4786 return;
4787 UIAccessibilityPostNotification(
4788 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4789 }];
4790}
4791
4792- (BOOL)shouldAutorotate {
4793 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4794 // Don't rotate if a voice search is being presented or dismissed. Once the
4795 // transition animations finish, only the Voice Search UIViewController's
4796 // |-shouldAutorotate| will be called.
4797 return NO;
4798 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4799 // Don't auto rotate if side swipe controller view says not to.
4800 return NO;
4801 } else {
4802 return [super shouldAutorotate];
4803 }
4804}
4805
4806// Always return yes, as this tap should work with various recognizers,
4807// including UITextTapRecognizer, UILongPressGestureRecognizer,
4808// UIScrollViewPanGestureRecognizer and others.
4809- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4810 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4811 (UIGestureRecognizer*)otherGestureRecognizer {
4812 return YES;
4813}
4814
4815// Tap gestures should only be recognized within |_contentArea|.
4816- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4817 CGPoint location = [gesture locationInView:self.view];
4818
4819 // Only allow touches on descendant views of |_contentArea|.
4820 UIView* hitView = [self.view hitTest:location withEvent:nil];
4821 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4822}
4823
4824#pragma mark - SideSwipeController Delegate Methods
4825
4826- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4827 DCHECK(!IsIPadIdiom());
4828 // Update frame incase orientation changed while |_contentArea| was out of
4829 // the view hierarchy.
4830 [_contentArea setFrame:[sideSwipeView frame]];
4831
4832 [self.view insertSubview:_contentArea atIndex:0];
4833 [self updateVoiceSearchBarVisibilityAnimated:NO];
4834 [self updateToolbar];
4835
4836 // Reset horizontal stack view.
4837 [sideSwipeView removeFromSuperview];
4838 [_sideSwipeController setInSwipe:NO];
4839 [_infoBarContainer->view() setHidden:NO];
4840}
4841
4842- (UIView*)contentView {
4843 return _contentArea;
4844}
4845
sdefresnee65fd872016-12-19 13:38:134846- (WebToolbarController*)toolbarController {
4847 return _toolbarController;
4848}
4849
4850- (BOOL)preventSideSwipe {
4851 if ([_toolbarController toolsPopupController])
4852 return YES;
4853
4854 if (_voiceSearchController && _voiceSearchController->IsVisible())
4855 return YES;
4856
sdefresnee65fd872016-12-19 13:38:134857 if (!self.active)
4858 return YES;
4859
4860 return NO;
4861}
4862
4863- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4864 if (visible) {
4865 [self updateVoiceSearchBarVisibilityAnimated:NO];
4866 [self updateToolbar];
4867 [_infoBarContainer->view() setHidden:NO];
4868 } else {
4869 // Hide UI accessories such as find bar and first visit overlays
4870 // for welcome page.
4871 [self hideFindBarWithAnimation:NO];
4872 [_infoBarContainer->view() setHidden:YES];
4873 [_voiceSearchBar setHidden:YES];
4874 }
4875}
4876
4877- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4878 BOOL seenToolbar = NO;
4879 BOOL seenInfoBarContainer = NO;
4880 BOOL seenContentArea = NO;
4881 for (UIView* view in views.subviews) {
4882 if (view == [_toolbarController view])
4883 seenToolbar = YES;
4884 else if (view == _infoBarContainer->view())
4885 seenInfoBarContainer = YES;
4886 else if (view == _contentArea)
4887 seenContentArea = YES;
4888 if ((seenToolbar && !seenInfoBarContainer) ||
4889 (seenInfoBarContainer && !seenContentArea))
4890 return NO;
4891 }
4892 return YES;
4893}
4894
4895#pragma mark - PreloadControllerDelegate methods
4896
rohitraoeeb5293b2017-06-15 14:40:024897- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444898 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134899}
4900
rohitraoeeb5293b2017-06-15 14:40:024901- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
4902 return [self hasControllerForURL:url];
4903}
4904
sdefresnee65fd872016-12-19 13:38:134905#pragma mark - BookmarkBridgeMethods
4906
4907// If an added or removed bookmark is the same as the current url, update the
4908// toolbar so the star highlight is kept in sync.
4909- (void)bookmarkNodeModified:(const BookmarkNode*)node {
kkhorimotob110b262017-06-01 18:38:254910 if ([_model currentTab] &&
4911 node->url() == [_model currentTab].lastCommittedURL) {
sdefresnee65fd872016-12-19 13:38:134912 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:254913 }
sdefresnee65fd872016-12-19 13:38:134914}
4915
4916// If all bookmarks are removed, update the toolbar so the star highlight is
4917// kept in sync.
4918- (void)allBookmarksRemoved {
4919 [self updateToolbar];
4920}
4921
sdefresnee65fd872016-12-19 13:38:134922- (void)showErrorAlertWithStringTitle:(NSString*)title
4923 message:(NSString*)message {
4924 // Dismiss current alert.
4925 [_alertCoordinator stop];
4926
stkhapuginc9eee7b2017-04-10 15:49:274927 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
4928 message:message
4929 viewController:self];
sdefresnee65fd872016-12-19 13:38:134930 [_alertCoordinator start];
4931}
4932
4933- (void)showSnackbar:(NSString*)message {
4934 [_dependencyFactory showSnackbarWithMessage:message];
4935}
4936
4937#pragma mark - Show Mail Composer methods
4938
Gregory Chatzinoff5f9f7f02017-09-19 02:04:574939- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
4940 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:134941 if (![MFMailComposeViewController canSendMail]) {
4942 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:574943 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:134944 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:574945 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:134946 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
4947 return;
4948 }
stkhapuginc9eee7b2017-04-10 15:49:274949 MFMailComposeViewController* mailViewController =
4950 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:134951 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:574952 [mailViewController setToRecipients:[context toRecipients]];
4953 [mailViewController setSubject:[context subject]];
4954 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:134955
Gregory Chatzinoff5f9f7f02017-09-19 02:04:574956 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:134957 if (!textFile.empty()) {
4958 NSString* filename = base::SysUTF8ToNSString(textFile.value());
4959 NSData* data = [NSData dataWithContentsOfFile:filename];
4960 if (data) {
4961 NSString* displayName =
4962 base::SysUTF8ToNSString(textFile.BaseName().value());
4963 [mailViewController addAttachmentData:data
4964 mimeType:@"text/plain"
4965 fileName:displayName];
4966 }
4967 }
4968
4969 [mailViewController setMailComposeDelegate:self];
4970 [self presentViewController:mailViewController animated:YES completion:nil];
4971}
4972
4973#pragma mark - MFMailComposeViewControllerDelegate methods
4974
4975- (void)mailComposeController:(MFMailComposeViewController*)controller
4976 didFinishWithResult:(MFMailComposeResult)result
4977 error:(NSError*)error {
4978 [self dismissViewControllerAnimated:YES completion:nil];
4979}
4980
4981#pragma mark - StoreKitLauncher methods
4982
4983- (void)productViewControllerDidFinish:
4984 (SKStoreProductViewController*)viewController {
4985 [self dismissViewControllerAnimated:YES completion:nil];
4986}
4987
4988- (void)openAppStore:(NSString*)appId {
4989 if (![appId length])
4990 return;
4991 NSDictionary* product =
4992 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:274993 SKStoreProductViewController* storeViewController =
4994 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:134995 [storeViewController setDelegate:self];
4996 [storeViewController loadProductWithParameters:product completionBlock:nil];
4997 [self presentViewController:storeViewController animated:YES completion:nil];
4998}
4999
5000#pragma mark - TabDialogDelegate methods
5001
sdefresnee65fd872016-12-19 13:38:135002- (void)cancelDialogForTab:(Tab*)tab {
5003 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5004}
5005
5006#pragma mark - FKFeedbackPromptDelegate methods
5007
5008- (void)userTappedRateApp:(UIView*)view {
5009 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275010 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135011}
5012
5013- (void)userTappedSendFeedback:(UIView*)view {
5014 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275015 _rateThisAppDialog = nil;
sczsb8f81c32017-08-29 15:45:495016 [self.dispatcher showReportAnIssue];
sdefresnee65fd872016-12-19 13:38:135017}
5018
5019- (void)userTappedDismiss:(UIView*)view {
5020 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275021 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135022}
5023
Gauthier Ambard8160349c2017-09-06 14:43:245024#pragma mark - IncognitoViewControllerDelegate
5025
5026- (void)setToolbarBackgroundAlpha:(CGFloat)alpha {
5027 [_toolbarController setBackgroundAlpha:alpha];
5028}
5029
sdefresnee65fd872016-12-19 13:38:135030#pragma mark - VoiceSearchBarDelegate
5031
5032- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275033 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135034 [self ensureVoiceSearchControllerCreated];
5035 return _voiceSearchController->IsTextToSpeechEnabled() &&
5036 _voiceSearchController->IsTextToSpeechSupported();
5037}
5038
5039- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275040 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135041 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5042}
5043
5044#pragma mark - VoiceSearchPresenter
5045
5046- (UIView*)voiceSearchButton {
5047 return _voiceSearchButton;
5048}
5049
5050- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5051 return [self currentLogoAnimationControllerOwner];
5052}
5053
Rohit Rao01e0e002017-08-14 20:49:435054#pragma mark - ActivityService Providers
5055
5056- (void)presentActivityServiceViewController:(UIViewController*)controller {
5057 [self presentViewController:controller animated:YES completion:nil];
5058}
5059
5060- (void)activityServiceDidEndPresenting {
5061 self.presenting = NO;
5062 [self.dialogPresenter tryToPresent];
5063}
5064
Rohit Raocda0a992017-08-16 15:37:115065#pragma mark - QRScanner Requirements
5066
5067- (void)presentQRScannerViewController:(UIViewController*)controller {
5068 [self presentViewController:controller animated:YES completion:nil];
5069}
5070
5071- (void)dismissQRScannerViewController:(UIViewController*)controller
5072 completion:(void (^)(void))completion {
5073 DCHECK_EQ(controller, self.presentedViewController);
5074 [self dismissViewControllerAnimated:YES completion:completion];
5075}
5076
sczsdd860eba2017-08-10 01:55:385077#pragma mark - TabHistoryPresenter
5078
sczs0a726d22017-08-21 22:40:135079- (UIView*)viewForTabHistoryPresentation {
5080 return self.view;
5081}
5082
sczsdd860eba2017-08-10 01:55:385083- (void)prepareForTabHistoryPresentation {
5084 DCHECK(self.visible || self.dismissingModal);
5085 [[self.tabModel currentTab].webController dismissKeyboard];
5086 [_toolbarController cancelOmniboxEdit];
5087}
5088
Mike Doughertya1ec26402017-08-23 19:46:315089#pragma mark - CaptivePortalDetectorTabHelperDelegate
5090
5091- (void)captivePortalBlockingPage:(IOSCaptivePortalBlockingPage*)blockingPage
5092 connectWithLandingURL:(GURL)landingURL {
5093 _captivePortalLoginCoordinator = [[CaptivePortalLoginCoordinator alloc]
5094 initWithBaseViewController:self
5095 landingURL:landingURL];
5096 [_captivePortalLoginCoordinator start];
5097}
5098
Gregory Chatzinoffdf93d692017-09-09 01:32:275099#pragma mark - PageInfoPresentation
5100
Gregory Chatzinoffb6a01f72017-09-20 20:06:395101- (void)presentPageInfoView:(UIView*)pageInfoView {
5102 [pageInfoView setFrame:self.view.bounds];
5103 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275104}
5105
5106- (void)prepareForPageInfoPresentation {
5107 // Dismiss the omnibox (if open).
5108 [_toolbarController cancelOmniboxEdit];
5109}
5110
Gregory Chatzinoffb6a01f72017-09-20 20:06:395111- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5112 return [self.view convertPoint:origin fromView:nil];
5113}
5114
Sylvain Defresnecacc3a52017-09-12 13:51:045115#pragma mark - WebStatePrinter
5116
5117- (void)printWebState:(web::WebState*)webState {
5118 if (webState == [_model currentTab].webState)
5119 [self printTab];
5120}
5121
Eugene But35ded552017-09-13 23:31:595122#pragma mark - RepostFormTabHelperDelegate
5123
5124- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
5125 presentRepostFromDialogAtPoint:(CGPoint)location
5126 completionHandler:(void (^)(BOOL))completion {
5127 _repostFormCoordinator = [[RepostFormCoordinator alloc]
5128 initWithBaseViewController:self
5129 dialogLocation:location
5130 webState:helper->web_state()
5131 completionHandler:completion];
5132 [_repostFormCoordinator start];
5133}
5134
5135- (void)repostFormTabHelperDismissRepostFormDialog:
5136 (RepostFormTabHelper*)helper {
5137 _repostFormCoordinator = nil;
5138}
5139
edchinf5150c682017-09-18 02:50:035140#pragma mark - TabStripPresentation
5141
5142- (BOOL)isTabStripFullyVisible {
5143 return ([self currentHeaderOffset] == 0.0f);
5144}
5145
5146- (void)showTabStripView:(UIView*)tabStripView {
5147 DCHECK([self isViewLoaded]);
5148 DCHECK(tabStripView);
5149 self.tabStripView = tabStripView;
5150 CGRect tabStripFrame = [self.tabStripView frame];
5151 tabStripFrame.origin = CGPointZero;
5152 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5153 // because the CGPointZero above will break reset the offset, but it's not
5154 // clear what removing that will do.
5155 tabStripFrame.origin.y = [self headerOffset];
5156 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5157 [self.tabStripView setFrame:tabStripFrame];
5158 [[self view] addSubview:tabStripView];
5159}
5160
sdefresnee65fd872016-12-19 13:38:135161@end