blob: 4b4d7e81d0f1da5008d50e3e0836ab37d48074bc [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"
Tommy Nyquistc1d6dea12017-07-26 20:37:2341#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0342#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2343#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1144#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1345#include "components/infobars/core/infobar_manager.h"
mathp9b4c11d2017-07-06 20:24:1346#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1347#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5448#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1349#include "components/search_engines/search_engines_pref_names.h"
50#include "components/search_engines/template_url_service.h"
51#include "components/sessions/core/tab_restore_service_helper.h"
52#include "components/strings/grit/components_strings.h"
53#include "components/toolbar/toolbar_model_impl.h"
54#include "ios/chrome/app/tests_hook.h"
55#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
56#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
57#include "ios/chrome/browser/chrome_url_constants.h"
58#include "ios/chrome/browser/chrome_url_util.h"
59#include "ios/chrome/browser/experimental_flags.h"
60#import "ios/chrome/browser/favicon/favicon_loader.h"
61#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2362#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
63#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1364#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
65#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3666#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1367#include "ios/chrome/browser/first_run/first_run.h"
68#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
69#include "ios/chrome/browser/infobars/infobar_container_ios.h"
70#include "ios/chrome/browser/infobars/infobar_container_view.h"
71#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
72#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1373#import "ios/chrome/browser/open_url_util.h"
74#import "ios/chrome/browser/passwords/password_controller.h"
sdefresnee65fd872016-12-19 13:38:1375#include "ios/chrome/browser/pref_names.h"
olivierrobin013ba672017-03-01 21:16:2476#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1377#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
78#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
79#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
80#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
81#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
82#import "ios/chrome/browser/snapshots/snapshot_cache.h"
83#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
84#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
pkld6e73e52017-03-08 15:56:5185#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:2886#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1387#import "ios/chrome/browser/tabs/tab.h"
88#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:1989#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:1390#import "ios/chrome/browser/tabs/tab_model.h"
91#import "ios/chrome/browser/tabs/tab_model_observer.h"
92#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
jif4a8cf942017-02-03 12:05:2493#import "ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h"
sdefresnee65fd872016-12-19 13:38:1394#import "ios/chrome/browser/ui/activity_services/share_protocol.h"
95#import "ios/chrome/browser/ui/activity_services/share_to_data.h"
jif4a8cf942017-02-03 12:05:2496#import "ios/chrome/browser/ui/activity_services/share_to_data_builder.h"
sdefresnee65fd872016-12-19 13:38:1397#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
98#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
99#import "ios/chrome/browser/ui/background_generator.h"
100#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
101#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13102#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
103#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
104#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
Mark Cogan5e3da152017-07-11 15:57:30105#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24106#import "ios/chrome/browser/ui/commands/browser_commands.h"
sdefresnee65fd872016-12-19 13:38:13107#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
108#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
Mark Cogandfcdea72017-07-18 13:47:38109#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13110#import "ios/chrome/browser/ui/commands/open_url_command.h"
111#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
112#import "ios/chrome/browser/ui/commands/show_mail_composer_command.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00113#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
sdefresnee65fd872016-12-19 13:38:13114#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13115#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
116#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
117#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
118#import "ios/chrome/browser/ui/external_file_controller.h"
119#import "ios/chrome/browser/ui/external_file_remover.h"
sdefresnee65fd872016-12-19 13:38:13120#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
121#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
122#import "ios/chrome/browser/ui/fullscreen_controller.h"
123#import "ios/chrome/browser/ui/history/tab_history_cell.h"
124#import "ios/chrome/browser/ui/key_commands_provider.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
126#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_panel_view_controller.h"
127#include "ios/chrome/browser/ui/omnibox/page_info_model.h"
128#import "ios/chrome/browser/ui/omnibox/page_info_view_controller.h"
129#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
130#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37131#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13132#import "ios/chrome/browser/ui/preload_controller.h"
133#import "ios/chrome/browser/ui/preload_controller_delegate.h"
134#import "ios/chrome/browser/ui/print/print_controller.h"
135#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
136#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03137#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13138#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13139#include "ios/chrome/browser/ui/rtl_geometry.h"
140#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
141#import "ios/chrome/browser/ui/stack_view/card_view.h"
142#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
143#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
144#import "ios/chrome/browser/ui/sync/sync_util.h"
145#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
146#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
147#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
148#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
149#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sdefresnee65fd872016-12-19 13:38:13150#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
151#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
152#include "ios/chrome/browser/ui/ui_util.h"
153#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28154#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13155#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
156#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51157#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13158#import "ios/chrome/browser/web/error_page_content.h"
159#import "ios/chrome/browser/web/passkit_dialog_provider.h"
eugenebutcae3d9e62017-01-27 20:01:05160#import "ios/chrome/browser/web/repost_form_tab_helper.h"
sdefresne62a00bb2017-04-10 15:36:05161#import "ios/chrome/browser/web_state_list/web_state_list.h"
162#import "ios/chrome/browser/web_state_list/web_state_opener.h"
sdefresnee65fd872016-12-19 13:38:13163#include "ios/chrome/grit/ios_chromium_strings.h"
164#include "ios/chrome/grit/ios_strings.h"
165#import "ios/net/request_tracker.h"
166#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
167#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
168#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
169#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
170#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
171#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
172#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
173#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
justincohen75011c32017-04-28 16:31:39174#import "ios/shared/chrome/browser/ui/commands/command_dispatcher.h"
sczs206ca2c2017-04-13 16:37:28175#import "ios/shared/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13176#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13177#include "ios/web/public/navigation_item.h"
178#import "ios/web/public/navigation_manager.h"
179#include "ios/web/public/referrer_util.h"
180#include "ios/web/public/ssl_status.h"
181#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39182#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13183#include "ios/web/public/web_client.h"
184#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13185#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29186#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13187#include "ios/web/public/web_state/web_state.h"
188#import "ios/web/public/web_state/web_state_delegate_bridge.h"
189#include "ios/web/public/web_thread.h"
190#import "ios/web/web_state/ui/crw_web_controller.h"
191#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17192#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13193#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
194#include "net/ssl/ssl_info.h"
195#include "net/url_request/url_request_context_getter.h"
196#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
197#include "ui/base/l10n/l10n_util.h"
198#include "ui/base/l10n/l10n_util_mac.h"
199#include "ui/base/page_transition_types.h"
200#include "url/gurl.h"
201
stkhapuginf58b10d02017-04-10 13:36:17202#if !defined(__has_feature) || !__has_feature(objc_arc)
203#error "This file requires ARC support."
204#endif
205
sdefresnee65fd872016-12-19 13:38:13206using base::UserMetricsAction;
207using bookmarks::BookmarkNode;
208
209class BrowserBookmarkModelBridge;
210class InfoBarContainerDelegateIOS;
211
212namespace ios_internal {
213NSString* const kPageInfoWillShowNotification =
214 @"kPageInfoWillShowNotification";
215NSString* const kPageInfoWillHideNotification =
216 @"kPageInfoWillHideNotification";
217NSString* const kLocationBarBecomesFirstResponderNotification =
218 @"kLocationBarBecomesFirstResponderNotification";
219NSString* const kLocationBarResignsFirstResponderNotification =
220 @"kLocationBarResignsFirstResponderNotification";
221} // namespace ios_internal
222
223namespace {
224
225typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
226 // Note: these values must match the ContextMenuOption enum in histograms.xml.
227 ACTION_OPEN_IN_NEW_TAB = 0,
228 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
229 ACTION_COPY_LINK_ADDRESS = 2,
230 ACTION_SAVE_IMAGE = 6,
231 ACTION_OPEN_IMAGE = 7,
232 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
233 ACTION_SEARCH_BY_IMAGE = 11,
234 ACTION_OPEN_JAVASCRIPT = 21,
235 ACTION_READ_LATER = 22,
236 NUM_ACTIONS = 23,
237};
238
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28239void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13240 if (is_image) {
241 if (is_link) {
242 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
243 NUM_ACTIONS);
244 } else {
245 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
246 NUM_ACTIONS);
247 }
248 } else {
249 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
250 NUM_ACTIONS);
251 }
252}
253
sdefresnee65fd872016-12-19 13:38:13254const CGFloat kVoiceSearchBarHeight = 59.0;
255
256// Dimensions to use when downsizing an image for search-by-image.
257const CGFloat kSearchByImageMaxImageArea = 90000.0;
258const CGFloat kSearchByImageMaxImageWidth = 600.0;
259const CGFloat kSearchByImageMaxImageHeight = 400.0;
260
261// The delay, in seconds, after startup before cleaning up the files received
262// from other applications that are not bookmarked nor referenced by an open or
263// recently closed tab.
264const int kExternalFilesCleanupDelaySeconds = 60;
265
266enum HeaderBehaviour {
267 // The header moves completely out of the screen.
268 Hideable = 0,
269 // This header stays on screen and doesn't overlap with the content.
270 Visible,
271 // This header stay on screen and covers part of the content.
272 Overlap
273};
274
sdefresnee65fd872016-12-19 13:38:13275const CGFloat kIPadFindBarOverlap = 11;
276
277bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24278 // Most URLs are allowed in incognito; the following is an exception.
279 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13280}
281
282// Temporary key to use when storing native controllers vended to tabs before
283// they are added to the tab model.
284NSString* const kNativeControllerTemporaryKey = @"NativeControllerTemporaryKey";
285
rohitrao005a6432017-03-16 20:52:42286} // namespace
sdefresnee65fd872016-12-19 13:38:13287
stkhapugin952ecef2017-04-11 12:11:45288#pragma mark - HeaderDefinition helper
289
290@interface HeaderDefinition : NSObject
291
292// The header view.
293@property(nonatomic, strong) UIView* view;
294// How to place the view, and its behaviour when the headers move.
295@property(nonatomic, assign) HeaderBehaviour behaviour;
296// Reduces the height of a header to adjust for shadows.
297@property(nonatomic, assign) CGFloat heightAdjustement;
298// Nudges that particular header up by this number of points.
299@property(nonatomic, assign) CGFloat inset;
300
301- (instancetype)initWithView:(UIView*)view
302 headerBehaviour:(HeaderBehaviour)behaviour
303 heightAdjustment:(CGFloat)heightAdjustment
304 inset:(CGFloat)inset;
305
306+ (instancetype)definitionWithView:(UIView*)view
307 headerBehaviour:(HeaderBehaviour)behaviour
308 heightAdjustment:(CGFloat)heightAdjustment
309 inset:(CGFloat)inset;
310
311@end
312
313@implementation HeaderDefinition
314@synthesize view = _view;
315@synthesize behaviour = _behaviour;
316@synthesize heightAdjustement = _heightAdjustement;
317@synthesize inset = _inset;
318
319+ (instancetype)definitionWithView:(UIView*)view
320 headerBehaviour:(HeaderBehaviour)behaviour
321 heightAdjustment:(CGFloat)heightAdjustment
322 inset:(CGFloat)inset {
323 return [[self alloc] initWithView:view
324 headerBehaviour:behaviour
325 heightAdjustment:heightAdjustment
326 inset:inset];
327}
328
329- (instancetype)initWithView:(UIView*)view
330 headerBehaviour:(HeaderBehaviour)behaviour
331 heightAdjustment:(CGFloat)heightAdjustment
332 inset:(CGFloat)inset {
333 self = [super init];
334 if (self) {
335 _view = view;
336 _behaviour = behaviour;
337 _heightAdjustement = heightAdjustment;
338 _inset = inset;
339 }
340 return self;
341}
342
343@end
344
345#pragma mark - BVC
346
sdefresnee65fd872016-12-19 13:38:13347@interface BrowserViewController ()<AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13348 CRWNativeContentProvider,
349 CRWWebStateDelegate,
350 DialogPresenterDelegate,
351 FullScreenControllerDelegate,
352 KeyCommandsPlumbing,
353 MFMailComposeViewControllerDelegate,
354 NewTabPageControllerObserver,
355 OverscrollActionsControllerDelegate,
356 PassKitDialogProvider,
357 PreloadControllerDelegate,
358 ShareToDelegate,
359 SKStoreProductViewControllerDelegate,
360 SnapshotOverlayProvider,
361 StoreKitLauncher,
362 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19363 TabHeadersDelegate,
sdefresnee65fd872016-12-19 13:38:13364 TabModelObserver,
365 TabSnapshottingDelegate,
366 UIGestureRecognizerDelegate,
367 UpgradeCenterClientProtocol,
368 VoiceSearchBarDelegate,
369 VoiceSearchBarOwner> {
370 // The dependency factory passed on initialization. Used to vend objects used
371 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27372 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13373
374 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27375 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13376
377 // Facade objects used by |_toolbarController|.
378 // Must outlive |_toolbarController|.
379 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
380 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
381
382 // Preload controller. Must outlive |_toolbarController|.
stkhapuginc9eee7b2017-04-10 15:49:27383 PreloadController* _preloadController;
sdefresnee65fd872016-12-19 13:38:13384
385 // The WebToolbarController used to display the omnibox.
stkhapuginc9eee7b2017-04-10 15:49:27386 WebToolbarController* _toolbarController;
sdefresnee65fd872016-12-19 13:38:13387
388 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27389 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13390
391 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27392 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13393
394 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27395 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13396
397 // Handles presentation of JavaScript dialogs.
398 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
399
justincohen75011c32017-04-28 16:31:39400 // Handles command dispatching.
401 CommandDispatcher* _dispatcher;
402
sdefresnee65fd872016-12-19 13:38:13403 // Keyboard commands provider. It offloads most of the keyboard commands
404 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27405 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13406
407 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
408 // to |-reparentToolbarController| will reset it to NO.
409 BOOL _isToolbarControllerRelinquished;
410
411 // The controller that owns the currently relinquished toolbar controller.
412 // The reference is weak because it's possible for the toolbar owner to be
413 // deallocated mid-animation due to memory pressure or a tab being closed
414 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27415 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13416
417 // Always present on tablet; always nil on phone.
stkhapuginc9eee7b2017-04-10 15:49:27418 TabStripController* _tabStripController;
sdefresnee65fd872016-12-19 13:38:13419
sdefresnee65fd872016-12-19 13:38:13420 // Used to inject Javascript implementing the PaymentRequest API and to
421 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27422 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13423
424 // Used to display the Page Info UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27425 PageInfoViewController* _pageInfoController;
sdefresnee65fd872016-12-19 13:38:13426
427 // Used to display the Voice Search UI. Nil if not visible.
428 scoped_refptr<VoiceSearchController> _voiceSearchController;
429
430 // Used to display the QR Scanner UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27431 QRScannerViewController* _qrScannerViewController;
sdefresnee65fd872016-12-19 13:38:13432
gambard6299cc1d2017-02-21 13:06:03433 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27434 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03435
sdefresnee65fd872016-12-19 13:38:13436 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27437 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13438
sdefresnee65fd872016-12-19 13:38:13439 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27440 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13441
442 // Records the set of domains for which full screen alert has already been
443 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27444 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13445
446 // Adapter to let BVC be the delegate for WebState.
447 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
448
449 // YES if new tab is animating in.
450 BOOL _inNewTabAnimation;
451
452 // YES if Voice Search should be started when the new tab animation is
453 // finished.
454 BOOL _startVoiceSearchAfterNewTabAnimation;
455
456 // YES if the user interacts with the location bar.
457 BOOL _locationBarHasFocus;
458 // YES if a load was cancelled due to typing in the location bar.
459 BOOL _locationBarEditCancelledLoad;
460 // YES if waiting for a foreground tab due to expectNewForegroundTab.
461 BOOL _expectingForegroundTab;
462
Sylvain Defresne41170aa2017-06-15 10:25:20463 // Whether or not -shutdown has been called.
464 BOOL _isShutdown;
465
sdefresnee65fd872016-12-19 13:38:13466 // The ChromeBrowserState associated with this BVC.
467 ios::ChromeBrowserState* _browserState; // weak
468
469 // Whether or not Incognito* is enabled.
470 BOOL _isOffTheRecord;
471
472 // The last point within |_contentArea| that's received a touch.
473 CGPoint _lastTapPoint;
474
475 // The time at which |_lastTapPoint| was most recently set.
476 CFTimeInterval _lastTapTime;
477
478 // A single infobar container handles all infobars in all tabs. It keeps
479 // track of infobars for current tab (accessed via infobar helper of
480 // the current tab).
481 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
482
483 // Bridge class to deliver container change notifications to BVC.
484 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
485
486 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37487 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27488 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13489
490 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11491 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13492
493 // Card side swipe view.
stkhapuginc9eee7b2017-04-10 15:49:27494 CardSideSwipeView* _sideSwipeView;
sdefresnee65fd872016-12-19 13:38:13495
sdefresnee65fd872016-12-19 13:38:13496 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27497 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13498
499 // Bridge to register for bookmark changes.
500 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
501
502 // Cached pointer to the bookmarks model.
503 bookmarks::BookmarkModel* _bookmarkModel; // weak
504
505 // The controller that shows the bookmarking UI after the user taps the star
506 // button.
stkhapuginc9eee7b2017-04-10 15:49:27507 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13508
509 // Used to remove unreferenced external files.
510 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
511
512 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27513 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13514
515 // Maps tab IDs to the most recent native content controller vended to that
516 // tab's web controller.
stkhapuginc9eee7b2017-04-10 15:49:27517 NSMapTable* _nativeControllersForTabIDs;
sdefresnee65fd872016-12-19 13:38:13518
519 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27520 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13521
522 // The sender for the last received IDC_VOICE_SEARCH command.
stkhapuginc9eee7b2017-04-10 15:49:27523 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13524
525 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27526 AlertCoordinator* _alertCoordinator;
sdefresnee65fd872016-12-19 13:38:13527}
528
529// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17530@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13531// The browser's preload controller.
stkhapuginf58b10d02017-04-10 13:36:17532@property(nonatomic, strong, readonly) PreloadController* preloadController;
sdefresnee65fd872016-12-19 13:38:13533// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17534@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13535// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17536@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13537// Whether the current tab can enable the reader mode menu item.
538@property(nonatomic, assign, readonly) BOOL canUseReaderMode;
539// Whether the current tab can enable the request desktop menu item.
540@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
541// Whether the sharing menu should be enabled.
542@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
543// Helper method to check web controller canShowFindBar method.
544@property(nonatomic, assign, readonly) BOOL canShowFindBar;
545// Whether the controller's view is currently available.
546// YES from viewWillAppear to viewWillDisappear.
547@property(nonatomic, assign, getter=isVisible) BOOL visible;
548// Whether the controller's view is currently visible.
549// YES from viewDidAppear to viewWillDisappear.
550@property(nonatomic, assign) BOOL viewVisible;
551// Whether the controller is currently dismissing a presented view controller.
552@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
553// Returns YES if the toolbar has not been scrolled out by fullscreen.
554@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
555 BOOL toolbarOnScreen;
556// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12557@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13558// Whether BVC prefers to hide the status bar. This value is used to determine
559// the response from the |prefersStatusBarHidden| method.
560@property(nonatomic, assign) BOOL hideStatusBar;
561// Whether the VoiceSearchBar should be displayed.
562@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
563// Coordinator for displaying a modal overlay with activity indicator to prevent
564// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17565@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13566 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39567// A block to be run when the |tabWasAdded:| method completes the animation
568// for the presentation of a new tab. Can be used to record performance metrics.
569@property(nonatomic, strong, nullable)
570 ProceduralBlock foregroundTabWasAddedCompletionBlock;
sdefresnee65fd872016-12-19 13:38:13571
liaoyukeea9f3ee62017-03-07 22:05:39572// The user agent type used to load the currently visible page. User agent type
573// is NONE if there is no visible page or visible page is a native page.
574@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
575
stkhapugin952ecef2017-04-11 12:11:45576// Returns the header views, all the chrome on top of the page, including the
577// ones that cannot be scrolled off screen by full screen.
578@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
579
sdefresnee65fd872016-12-19 13:38:13580// BVC initialization:
581// If the BVC is initialized with a valid browser state & tab model immediately,
582// the path is straightforward: functionality is enabled, and the UI is built
583// when -viewDidLoad is called.
584// If the BVC is initialized without a browser state or tab model, the tab model
585// and browser state may or may not be provided before -viewDidLoad is called.
586// In most cases, they will not, to improve startup performance.
587// In order to handle this, initialization of various aspects of BVC have been
588// broken out into the following functions, which have expectations (enforced
589// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
590
591// Registers for notifications.
592- (void)registerForNotifications;
593// Called when a tab is starting to load. If it's a link click or form
594// submission, the user is navigating away from any entries in the forward
595// history. Tell the toolbar so it can update the UI appropriately.
596// See the warning on [Tab webWillStartLoadingURL] about invocation of this
597// method sequence by malicious pages.
598- (void)pageLoadStarting:(NSNotification*)notify;
599// Called when a tab actually starts loading.
600- (void)pageLoadStarted:(NSNotification*)notify;
601// Called when a tab finishes loading. Update the Omnibox with the url and
602// stop any page load progess display.
603- (void)pageLoadComplete:(NSNotification*)notify;
604// Called when a tab is deselected in the model.
605// This notification also occurs when a tab is closed.
606- (void)tabDeselected:(NSNotification*)notify;
607// Animates sliding current tab and rotate-entering new tab while new tab loads
608// in background on the iPhone only.
609- (void)tabWasAdded:(NSNotification*)notify;
610
611// Updates non-view-related functionality with the given browser state and tab
612// model.
613// Does not matter whether or not the view has been loaded.
614- (void)updateWithTabModel:(TabModel*)model
615 browserState:(ios::ChromeBrowserState*)browserState;
616// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
617// the status bar to mimic this layout.
618- (void)installFakeStatusBar;
619// Builds the UI parts of tab strip and the toolbar. Does not matter whether
620// or not browser state and tab model are valid.
621- (void)buildToolbarAndTabStrip;
622// Updates view-related functionality with the given tab model and browser
623// state. The view must have been loaded. Uses |_browserState| and |_model|.
624- (void)addUIFunctionalityForModelAndBrowserState;
625// Sets the correct frame and heirarchy for subviews and helper views.
626- (void)setUpViewLayout;
627// Sets the correct frame for the tab strip based on the given maximum width.
628- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
629// Makes |tab| the currently visible tab, displaying its view. Calls
630// -selectedTabChanged on the toolbar only if |newSelection| is YES.
631- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
632// Initializes the bookmark interaction controller if not already initialized.
633- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13634// Add all delegates to the provided |tab|.
635- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14636// Remove delegates from the provided |tab|.
637- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13638// Closes the current tab, with animation if applicable.
639- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13640// Shows the menu to initiate sharing |data|.
641- (void)sharePageWithData:(ShareToData*)data;
642// Convenience method to share the current page.
643- (void)sharePage;
sdefresnee65fd872016-12-19 13:38:13644// Shows the Online Help Page in a tab.
645- (void)showHelpPage;
646// Show the bookmarks page.
647- (void)showAllBookmarks;
648// Shows a panel within the New Tab Page.
649- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel;
650// Shows the "rate this app" dialog.
651- (void)showRateThisAppDialog;
652// Dismisses the "rate this app" dialog.
653- (void)dismissRateThisAppDialog;
654#if !defined(NDEBUG)
655// Shows the source of the current page.
656- (void)viewSource;
657#endif
olivierrobin889af53f2017-03-01 14:56:32658// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13659- (BOOL)isTabNativePage:(Tab*)tab;
660// Returns the view to use when animating a page in or out, positioning it to
661// fill the content area but not actually adding it to the view hierarchy.
662- (UIImageView*)pageOpenCloseAnimationView;
663// Returns the view to use when animating full screen NTP paper in, filling the
664// entire screen but not actually adding it to the view hierarchy.
665- (UIImageView*)pageFullScreenOpenCloseAnimationView;
666// Updates the toolbar display based on the current tab.
667- (void)updateToolbar;
668// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12669// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13670- (void)updateDialogPresenterActiveState;
671// Dismisses popups and modal dialogs that are displayed above the BVC upon size
672// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
673// is performed.
674// TODO(crbug.com/522721): Support size changes for all popups and modal
675// dialogs.
676- (void)dismissPopups;
677// Create and show the find bar.
678- (void)initFindBarForTab;
679// Search for find bar query string.
680- (void)searchFindInPage;
681// Update find bar with model data. If |shouldFocus| is set to YES, the text
682// field will become first responder.
683- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
684// Close and disable find in page bar.
685- (void)closeFindInPage;
686// Hide find bar.
687- (void)hideFindBarWithAnimation:(BOOL)animate;
688// Shows find bar. If |selectText| is YES, all text inside the Find Bar
689// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
690// set to be first responder.
691- (void)showFindBarWithAnimation:(BOOL)animate
692 selectText:(BOOL)selectText
693 shouldFocus:(BOOL)shouldFocus;
694// Show the Page Security Info.
695- (void)showPageInfoPopupForView:(UIView*)sourceView;
696// Hide the Page Security Info.
697- (void)hidePageInfoPopupForView:(UIView*)sourceView;
698// Shows the tab history popup containing the tab's backward history.
699- (void)showTabHistoryPopupForBackwardHistory;
700// Shows the tab history popup containing the tab's forward history.
701- (void)showTabHistoryPopupForForwardHistory;
702// Navigate back/forward to the selected entry in the tab's history.
703- (void)navigateToSelectedEntry:(id)sender;
704// The infobar state (typically height) has changed.
705- (void)infoBarContainerStateChanged:(bool)is_animating;
706// Adds a CardView on top of the contentArea either taking the size of the full
707// screen or just the size of the space under the header.
708// Returns the CardView that was added.
709- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
710// Called when either a tab finishes loading or when a tab with finished content
711// is added directly to the model via pre-rendering. The tab must be non-nil and
712// must be a member of the tab model controlled by this BrowserViewController.
713- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
714// Evaluates Javascript asynchronously using the current page context.
715- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13716// Helper methods used by ShareToDelegate methods.
717// Shows an alert with the given title and message id.
718- (void)showErrorAlert:(int)titleMessageId message:(int)messageId;
719// Helper method displaying an alert with the given title and message.
720// Dismisses previous alert if it has not been dismissed yet.
721- (void)showErrorAlertWithStringTitle:(NSString*)title
722 message:(NSString*)message;
723// Shows a self-dismissing snackbar displaying |message|.
724- (void)showSnackbar:(NSString*)message;
725// Induces an intentional crash in the browser process.
726- (void)induceBrowserCrash;
727// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17728- (void)managePermissionAndSaveImage:(NSData*)data
729 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13730// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24731// saved as a temporary file on disk then saved in photos. Saving will happen
732// on a background sequence and the completion block will be invoked on that
733// sequence.
734- (void)saveImage:(NSData*)data
735 withFileExtension:(NSString*)fileExtension
736 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13737// Called when Chrome has been denied access to the photos or videos and the
738// user can change it.
739// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
740// settings. Dismiss previous alert if it has not been dismissed yet.
741- (void)displayImageErrorAlertWithSettingsOnMainQueue;
742// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
743// previous alert if it has not been dismissed yet.
744- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
745// Called when Chrome has been denied access to the photos or videos and the
746// user cannot change it.
747// Shows a privacy alert on the main queue, with errorContent as the message.
748// Dismisses previous alert if it has not been dismissed yet.
749- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
750// Called with the results of saving a picture in the photo album. If error is
751// nil the save succeeded.
752- (void)finishSavingImageWithError:(NSError*)error;
753// Provides a view that encompasses currently displayed infobar(s) or nil
754// if no infobar is presented.
755- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
756// Returns a vertical infobar offset relative to the tab content.
757- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
758// Provides a view that encompasses the voice search bar if it's displayed or
759// nil if the voice search bar isn't displayed.
760- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
761// Returns a vertical voice search bar offset relative to the tab content.
762- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
763// Lazily instantiates |_voiceSearchController|.
764- (void)ensureVoiceSearchControllerCreated;
765// Lazily instantiates |_voiceSearchBar| and adds it to the view.
766- (void)ensureVoiceSearchBarCreated;
767// Shows/hides the voice search bar.
768- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
769// The LogoAnimationControllerOwner to be used for the next logo transition
770// animation.
771- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13772// Returns the footer view if one exists (e.g. the voice search bar).
773- (UIView*)footerView;
774// Returns the height of the header view for the tab model's current tab.
775- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13776// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45777- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13778 atOffset:(CGFloat)headerOffset;
779// Returns the y coordinate for the footer's frame when animating the footer
780// in/out of fullscreen.
781- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
782// Called when the animation for setting the header view's offset is finished.
783// |completed| should indicate if the animation finished completely or was
784// interrupted. |offset| should indicate the header offset after the animation.
785// |dragged| should indicate if the header moved due to the user dragging.
786- (void)fullScreenController:(FullScreenController*)controller
787 headerAnimationCompleted:(BOOL)completed
788 offset:(CGFloat)offset;
789// Performs a search with the image at the given url. The referrer is used to
790// download the image.
791- (void)searchByImageAtURL:(const GURL&)url
792 referrer:(const web::Referrer)referrer;
793// Saves the image at the given URL on the system's album. The referrer is used
794// to download the image.
795- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
796
Mark Cogandfcdea72017-07-18 13:47:38797// Record the last tap point based on the |originPoint| (if any) passed in
798// |command|.
799- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13800// Get return the last stored |_lastTapPoint| if it's been set within the past
801// second.
802- (CGPoint)lastTapPoint;
803// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
804- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
805// Returns the native controller being used by |tab|'s web controller.
806- (id)nativeControllerForTab:(Tab*)tab;
807// Installs the BVC as overscroll actions controller of |nativeContent| if
808// needed. Sets the style of the overscroll actions toolbar.
809- (void)setOverScrollActionControllerToStaticNativeContent:
810 (StaticHtmlNativeContent*)nativeContent;
811// Whether the BVC should declare keyboard commands.
812- (BOOL)shouldRegisterKeyboardCommands;
813// Adds the given url to the reading list.
814- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
815@end
816
817class InfoBarContainerDelegateIOS
818 : public infobars::InfoBarContainer::Delegate {
819 public:
820 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
821 : controller_(controller) {}
822
823 ~InfoBarContainerDelegateIOS() override {}
824
825 private:
826 SkColor GetInfoBarSeparatorColor() const override {
827 NOTIMPLEMENTED();
828 return SK_ColorBLACK;
829 }
830
831 int ArrowTargetHeightForInfoBar(
832 size_t index,
833 const gfx::SlideAnimation& animation) const override {
834 return 0;
835 }
836
837 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
838 int arrow_target_height,
839 int bar_target_height,
840 int* arrow_height,
841 int* arrow_half_width,
842 int* bar_height) const override {
843 DCHECK_NE(-1, bar_target_height)
844 << "Infobars don't have a default height on iOS";
845 *arrow_height = 0;
846 *arrow_half_width = 0;
847 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
848 }
849
850 void InfoBarContainerStateChanged(bool is_animating) override {
851 [controller_ infoBarContainerStateChanged:is_animating];
852 }
853
854 bool DrawInfoBarArrows(int* x) const override { return false; }
855
stkhapuginf58b10d02017-04-10 13:36:17856 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13857};
858
859// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
860@interface BrowserViewController (BookmarkBridgeMethods)
861// If a bookmark matching the currentTab url is added or moved, update the
862// toolbar state so the star highlight is in sync.
863- (void)bookmarkNodeModified:(const BookmarkNode*)node;
864- (void)allBookmarksRemoved;
865@end
866
867// Handle notification that bookmarks has been removed changed so we can update
868// the bookmarked star icon.
869class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
870 public:
871 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
872 : owner_(owner) {}
873
874 ~BrowserBookmarkModelBridge() override {}
875
876 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
877 const BookmarkNode* parent,
878 int old_index,
879 const BookmarkNode* node,
880 const std::set<GURL>& removed_urls) override {
881 [owner_ bookmarkNodeModified:node];
882 }
883
884 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
885 bool ids_reassigned) override {}
886
887 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
888 const BookmarkNode* old_parent,
889 int old_index,
890 const BookmarkNode* new_parent,
891 int new_index) override {}
892
893 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
894 const BookmarkNode* parent,
895 int index) override {
896 [owner_ bookmarkNodeModified:parent->GetChild(index)];
897 }
898
899 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
900 const BookmarkNode* node) override {}
901
902 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
903 const BookmarkNode* node) override {}
904
905 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
906 const BookmarkNode* node) override {}
907
908 void BookmarkAllUserNodesRemoved(
909 bookmarks::BookmarkModel* model,
910 const std::set<GURL>& removed_urls) override {
911 [owner_ allBookmarksRemoved];
912 }
913
914 private:
stkhapuginf58b10d02017-04-10 13:36:17915 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13916};
917
918@implementation BrowserViewController
919
920@synthesize contentArea = _contentArea;
921@synthesize typingShield = _typingShield;
922@synthesize active = _active;
923@synthesize visible = _visible;
924@synthesize viewVisible = _viewVisible;
925@synthesize dismissingModal = _dismissingModal;
926@synthesize hideStatusBar = _hideStatusBar;
927@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
928@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39929@synthesize foregroundTabWasAddedCompletionBlock =
930 _foregroundTabWasAddedCompletionBlock;
sdefresnee65fd872016-12-19 13:38:13931
932#pragma mark - Object lifecycle
933
Mark Cogan5e3da152017-07-11 15:57:30934- (instancetype)
935 initWithTabModel:(TabModel*)model
936 browserState:(ios::ChromeBrowserState*)browserState
937 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
938applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13939 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
940 if (self) {
941 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17942
stkhapuginc9eee7b2017-04-10 15:49:27943 _dependencyFactory = factory;
944 _nativeControllersForTabIDs = [NSMapTable strongToWeakObjectsMapTable];
945 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
946 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39947 _dispatcher = [[CommandDispatcher alloc] init];
948 [_dispatcher startDispatchingToTarget:self
949 forProtocol:@protocol(UrlLoader)];
950 [_dispatcher startDispatchingToTarget:self
951 forProtocol:@protocol(WebToolbarDelegate)];
952 [_dispatcher startDispatchingToTarget:self
953 forSelector:@selector(chromeExecuteCommand:)];
Mark Cogan6c58ea92017-07-06 13:08:24954 [_dispatcher startDispatchingToTarget:self
955 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30956 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
957 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32958 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
959 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
960 // dispatched to the endpoint as well. Since this is potentially
961 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
962 DCHECK(!applicationCommandEndpoint ||
963 [applicationCommandEndpoint
964 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
965 [_dispatcher
966 startDispatchingToTarget:applicationCommandEndpoint
967 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39968
sdefresnee65fd872016-12-19 13:38:13969 _javaScriptDialogPresenter.reset(
970 new JavaScriptDialogPresenterImpl(_dialogPresenter));
971 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
972 // TODO(leng): Delay this.
973 [[UpgradeCenter sharedInstance] registerClient:self];
974 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13975 if (model && browserState)
976 [self updateWithTabModel:model browserState:browserState];
977 if ([[NSUserDefaults standardUserDefaults]
978 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:27979 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:13980 }
981 }
982 return self;
983}
984
985- (instancetype)initWithNibName:(NSString*)nibNameOrNil
986 bundle:(NSBundle*)nibBundleOrNil {
987 NOTREACHED();
988 return nil;
989}
990
991- (instancetype)initWithCoder:(NSCoder*)aDecoder {
992 NOTREACHED();
993 return nil;
994}
995
996- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:20997 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:13998}
999
1000#pragma mark - Accessibility
1001
1002- (BOOL)accessibilityPerformEscape {
1003 [self dismissPopups];
1004 return YES;
1005}
1006
1007#pragma mark - Properties
1008
Mark Cogan5e3da152017-07-11 15:57:301009- (id<ApplicationCommands, BrowserCommands>)dispatcher {
1010 return static_cast<id<ApplicationCommands, BrowserCommands>>(_dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241011}
1012
sdefresnee65fd872016-12-19 13:38:131013- (void)setActive:(BOOL)active {
1014 if (_active == active) {
1015 return;
1016 }
1017 _active = active;
1018
1019 // If not active, display an activity indicator overlay over the view to
1020 // prevent interaction with the web page.
1021 // TODO(crbug.com/637093): This coordinator should be managed by the
1022 // coordinator used to present BrowserViewController, when implemented.
1023 if (active) {
1024 [self.activityOverlayCoordinator stop];
1025 self.activityOverlayCoordinator = nil;
1026 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171027 self.activityOverlayCoordinator =
1028 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131029 [self.activityOverlayCoordinator start];
1030 }
1031
1032 if (_browserState) {
1033 web::ActiveStateManager* active_state_manager =
1034 web::BrowserState::GetActiveStateManager(_browserState);
1035 active_state_manager->SetActive(active);
1036 }
1037
1038 [_model setWebUsageEnabled:active];
1039 [self updateDialogPresenterActiveState];
1040
1041 if (active) {
1042 // Make sure the tab (if any; it's possible to get here without a current
1043 // tab if the caller is about to create one) ends up on screen completely.
1044 Tab* currentTab = [_model currentTab];
1045 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441046 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131047 if (_expectingForegroundTab)
1048 [currentTab.webController setOverlayPreviewMode:YES];
1049 if (currentTab)
1050 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341051 } else {
1052 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131053 }
sdefresnee65fd872016-12-19 13:38:131054 [_paymentRequestManager enablePaymentRequest:active];
1055
1056 [self setNeedsStatusBarAppearanceUpdate];
1057}
1058
1059- (void)setPrimary:(BOOL)primary {
1060 [_model setPrimary:primary];
1061 if (primary) {
1062 [self updateDialogPresenterActiveState];
1063 } else {
1064 self.dialogPresenter.active = false;
1065 }
1066}
1067
1068- (BOOL)isPlayingTTS {
1069 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1070}
1071
sdefresne6165c8742017-01-16 15:42:021072- (ios::ChromeBrowserState*)browserState {
1073 return _browserState;
1074}
1075
1076- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271077 return _model;
sdefresne6165c8742017-01-16 15:42:021078}
1079
sdefresnee65fd872016-12-19 13:38:131080- (SideSwipeController*)sideSwipeController {
1081 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271082 _sideSwipeController =
1083 [[SideSwipeController alloc] initWithTabModel:_model
1084 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131085 [_sideSwipeController setSnapshotDelegate:self];
1086 [_sideSwipeController setSwipeDelegate:self];
1087 }
1088 return _sideSwipeController;
1089}
1090
1091- (PreloadController*)preloadController {
stkhapuginc9eee7b2017-04-10 15:49:271092 return _preloadController;
sdefresnee65fd872016-12-19 13:38:131093}
1094
1095- (DialogPresenter*)dialogPresenter {
1096 return _dialogPresenter;
1097}
1098
1099- (BOOL)canUseReaderMode {
1100 Tab* tab = [_model currentTab];
1101 if ([self isTabNativePage:tab])
1102 return NO;
1103
1104 return [tab canSwitchToReaderMode];
1105}
1106
1107- (BOOL)canUseDesktopUserAgent {
1108 Tab* tab = [_model currentTab];
1109 if ([self isTabNativePage:tab])
1110 return NO;
1111
1112 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441113 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131114}
1115
1116// Whether the sharing menu should be shown.
1117- (BOOL)canShowShareMenu {
kkhorimotob110b262017-06-01 18:38:251118 const GURL& URL = [_model currentTab].lastCommittedURL;
1119 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131120}
1121
1122- (BOOL)canShowFindBar {
1123 // Make sure web controller can handle find in page.
1124 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421125 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131126 return NO;
rohitrao005a6432017-03-16 20:52:421127 }
sdefresnee65fd872016-12-19 13:38:131128
rohitrao005a6432017-03-16 20:52:421129 auto* helper = FindTabHelper::FromWebState(tab.webState);
1130 return (helper && helper->CurrentPageSupportsFindInPage() &&
1131 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131132}
1133
liaoyukeea9f3ee62017-03-07 22:05:391134- (web::UserAgentType)userAgentType {
1135 web::WebState* webState = [_model currentTab].webState;
1136 if (!webState)
1137 return web::UserAgentType::NONE;
1138 web::NavigationItem* visibleItem =
1139 webState->GetNavigationManager()->GetVisibleItem();
1140 if (!visibleItem)
1141 return web::UserAgentType::NONE;
1142
1143 return visibleItem->GetUserAgentType();
1144}
1145
sdefresnee65fd872016-12-19 13:38:131146- (void)setVisible:(BOOL)visible {
1147 if (_visible == visible)
1148 return;
1149 _visible = visible;
1150}
1151
1152- (void)setViewVisible:(BOOL)viewVisible {
1153 if (_viewVisible == viewVisible)
1154 return;
1155 _viewVisible = viewVisible;
1156 self.visible = viewVisible;
1157 [self updateDialogPresenterActiveState];
1158}
1159
1160- (BOOL)isToolbarOnScreen {
1161 return [self headerHeight] - [self currentHeaderOffset] > 0;
1162}
1163
kkhorimotoa44349c12017-04-12 23:02:121164- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1165 if (_inNewTabAnimation == inNewTabAnimation)
1166 return;
1167 _inNewTabAnimation = inNewTabAnimation;
1168 [self updateDialogPresenterActiveState];
1169}
1170
sdefresnee65fd872016-12-19 13:38:131171- (BOOL)isInNewTabAnimation {
1172 return _inNewTabAnimation;
1173}
1174
1175- (BOOL)shouldShowVoiceSearchBar {
1176 // On iPads, the voice search bar should only be shown for regular horizontal
1177 // size class configurations. It should always be shown for voice search
1178 // results Tabs on iPhones, including configurations with regular horizontal
1179 // size classes (i.e. landscape iPhone 6 Plus).
1180 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1181 UIUserInterfaceSizeClassCompact;
1182 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1183 (!IsIPadIdiom() || compactWidth);
1184}
1185
1186- (void)setHideStatusBar:(BOOL)hideStatusBar {
1187 if (_hideStatusBar == hideStatusBar)
1188 return;
1189 _hideStatusBar = hideStatusBar;
1190 [self setNeedsStatusBarAppearanceUpdate];
1191}
1192
1193#pragma mark - IBActions
1194
1195- (void)shieldWasTapped:(id)sender {
1196 [_toolbarController cancelOmniboxEdit];
1197}
1198
sdefresnee65fd872016-12-19 13:38:131199#pragma mark - UIViewController methods
1200
1201// Perform additional set up after loading the view, typically from a nib.
1202- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281203 CGRect initialViewsRect = self.view.frame;
1204 initialViewsRect.origin.y += StatusBarHeight();
1205 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131206 UIViewAutoresizing initialViewAutoresizing =
1207 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1208
stkhapuginf58b10d02017-04-10 13:36:171209 self.contentArea =
1210 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131211 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171212 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131213 self.typingShield.autoresizingMask = initialViewAutoresizing;
1214 [self.typingShield addTarget:self
1215 action:@selector(shieldWasTapped:)
1216 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131217 self.view.autoresizingMask = initialViewAutoresizing;
1218 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1219 [self.view addSubview:self.contentArea];
1220 [self.view addSubview:self.typingShield];
1221 [super viewDidLoad];
1222
1223 // Install fake status bar for iPad iOS7
1224 [self installFakeStatusBar];
1225 [self buildToolbarAndTabStrip];
1226 [self setUpViewLayout];
1227 // If the tab model and browser state are valid, finish initialization.
1228 if (_model && _browserState)
1229 [self addUIFunctionalityForModelAndBrowserState];
1230
1231 // Add a tap gesture recognizer to save the last tap location for the source
1232 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271233 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1234 initWithTarget:self
1235 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131236 [tapRecognizer setDelegate:self];
1237 [tapRecognizer setCancelsTouchesInView:NO];
1238 [_contentArea addGestureRecognizer:tapRecognizer];
1239}
1240
1241- (void)viewDidAppear:(BOOL)animated {
1242 [super viewDidAppear:animated];
1243 self.viewVisible = YES;
1244 [self updateDialogPresenterActiveState];
1245}
1246
1247- (void)viewWillAppear:(BOOL)animated {
1248 [super viewWillAppear:animated];
1249
1250 // Reparent the toolbar if it's been relinquished.
1251 if (_isToolbarControllerRelinquished)
1252 [self reparentToolbarController];
1253
1254 self.visible = YES;
1255
1256 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251257 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131258 _infoBarContainer->RestoreInfobars();
1259 }
1260
1261 // If the controller is suspended, or has been paged out due to low memory,
1262 // updating the view will be handled when it's displayed again.
1263 if (![_model webUsageEnabled] || !self.contentArea)
1264 return;
1265 // Update the displayed tab (if any; the switcher may not have created one
1266 // yet) in case it changed while showing the switcher.
1267 Tab* currentTab = [_model currentTab];
1268 if (currentTab)
1269 [self displayTab:currentTab isNewSelection:YES];
1270}
1271
1272- (void)viewWillDisappear:(BOOL)animated {
1273 self.viewVisible = NO;
1274 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131275 [[_model currentTab] wasHidden];
1276 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251277 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131278 _infoBarContainer->SuspendInfobars();
1279 }
1280 [super viewWillDisappear:animated];
1281}
1282
1283- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1284 duration:(NSTimeInterval)duration {
1285 [super willRotateToInterfaceOrientation:orient duration:duration];
1286 [self dismissPopups];
1287 [self reshowFindBarIfNeededWithCoordinator:nil];
1288}
1289
1290- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1291 [super didRotateFromInterfaceOrientation:orient];
1292
1293 // This reinitializes the toolbar, including updating the Overlay View,
1294 // if there is one.
1295 [self updateToolbar];
1296 [self infoBarContainerStateChanged:false];
1297}
1298
1299- (BOOL)prefersStatusBarHidden {
1300 return self.hideStatusBar;
1301}
1302
1303// Called when in the foreground and the OS needs more memory. Release as much
1304// as possible.
1305- (void)didReceiveMemoryWarning {
1306 // Releases the view if it doesn't have a superview.
1307 [super didReceiveMemoryWarning];
1308
1309 // Release any cached data, images, etc that aren't in use.
1310 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1311 // but since the FaviconCache uses obj-c in the header, it can't be included
1312 // there.
1313 if (_browserState) {
1314 FaviconLoader* loader =
1315 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1316 _browserState);
1317 if (loader)
1318 loader->PurgeCache();
1319 }
1320
1321 if (![self isViewLoaded]) {
1322 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1323 // as the BrowserViewController.
1324 self.contentArea = nil;
1325 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271326 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131327 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271328 _qrScannerViewController = nil;
1329 _readingListCoordinator = nil;
1330 _toolbarController = nil;
1331 _toolbarModelDelegate = nil;
1332 _toolbarModelIOS = nil;
1333 _tabStripController = nil;
1334 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131335 }
1336}
1337
1338- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1339 [super traitCollectionDidChange:previousTraitCollection];
1340 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1341 // because in some cases the presented view controller isn't a child of the
1342 // BVC in the view controller hierarchy (some intervening object isn't a
1343 // view controller).
1344 [self.presentedViewController
1345 traitCollectionDidChange:previousTraitCollection];
1346 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1347 // Update voice search bar visibility.
1348 [self updateVoiceSearchBarVisibilityAnimated:NO];
1349}
1350
1351- (void)viewWillTransitionToSize:(CGSize)size
1352 withTransitionCoordinator:
1353 (id<UIViewControllerTransitionCoordinator>)coordinator {
1354 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1355 [self dismissPopups];
1356 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1357}
1358
1359- (void)reshowFindBarIfNeededWithCoordinator:
1360 (id<UIViewControllerTransitionCoordinator>)coordinator {
1361 if (![_findBarController isFindInPageShown])
1362 return;
1363
1364 // Record focused state.
1365 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1366
1367 [self hideFindBarWithAnimation:NO];
1368
stkhapuginc9eee7b2017-04-10 15:49:271369 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131370 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1371 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271372 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131373 if (strongSelf)
1374 [strongSelf showFindBarWithAnimation:NO
1375 selectText:NO
1376 shouldFocus:isFocusedBeforeReshow];
1377 };
1378
1379 BOOL enqueued =
1380 [coordinator animateAlongsideTransition:nil completion:completion];
1381 if (!enqueued) {
1382 completion(nil);
1383 }
1384}
1385
1386- (void)dismissViewControllerAnimated:(BOOL)flag
1387 completion:(void (^)())completion {
1388 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271389 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131390 [super dismissViewControllerAnimated:flag
1391 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271392 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131393 [strongSelf setDismissingModal:NO];
1394 [strongSelf setPresenting:NO];
1395 if (completion)
1396 completion();
1397 [[strongSelf dialogPresenter] tryToPresent];
1398 }];
1399}
1400
1401- (void)presentViewController:(UIViewController*)viewControllerToPresent
1402 animated:(BOOL)flag
1403 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271404 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131405 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1406 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1407 // presented, to show a temporary view of the launch screen and then remove it
1408 // when the controller for the FRE has been presented. This fix should be
1409 // removed when the FRE startup code is rewritten.
1410 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1411 experimental_flags::AlwaysDisplayFirstRun()) &&
1412 !tests_hook::DisableFirstRun();
1413 // These if statements check that |presentViewController| is being called for
1414 // the FRE case.
1415 if (firstRunLaunch &&
1416 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1417 UINavigationController* navController =
1418 base::mac::ObjCCastStrict<UINavigationController>(
1419 viewControllerToPresent);
1420 if ([navController.topViewController
1421 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1422 self.hideStatusBar = YES;
1423
1424 // Load view from Launch Screen and add it to window.
1425 NSBundle* mainBundle = base::mac::FrameworkBundle();
1426 NSArray* topObjects =
1427 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1428 UIViewController* launchScreenController =
1429 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1430 // |launchScreenView| is loaded as an autoreleased object, and is retained
1431 // by the |completion| block below.
1432 UIView* launchScreenView = launchScreenController.view;
1433 launchScreenView.userInteractionEnabled = NO;
1434 launchScreenView.frame = self.view.window.bounds;
1435 [self.view.window addSubview:launchScreenView];
1436
1437 // Replace the completion handler sent to the superclass with one which
1438 // removes |launchScreenView| and resets the status bar. If |completion|
1439 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271440 __weak BrowserViewController* weakSelf = self;
1441 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131442 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271443 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131444 if (completion)
1445 completion();
stkhapuginc9eee7b2017-04-10 15:49:271446 };
sdefresnee65fd872016-12-19 13:38:131447 }
1448 }
1449
1450 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171451 if ([_sideSwipeController inSwipe]) {
1452 [_sideSwipeController resetContentView];
1453 }
sdefresnee65fd872016-12-19 13:38:131454
1455 [super presentViewController:viewControllerToPresent
1456 animated:flag
1457 completion:finalCompletionHandler];
1458}
1459
1460#pragma mark - Notification handling
1461
1462- (void)registerForNotifications {
1463 DCHECK(_model);
1464 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1465 [defaultCenter addObserver:self
1466 selector:@selector(pageLoadStarting:)
1467 name:kTabModelTabWillStartLoadingNotification
1468 object:_model];
1469 [defaultCenter addObserver:self
1470 selector:@selector(pageLoadStarted:)
1471 name:kTabModelTabDidStartLoadingNotification
1472 object:_model];
1473 [defaultCenter addObserver:self
1474 selector:@selector(pageLoadComplete:)
1475 name:kTabModelTabDidFinishLoadingNotification
1476 object:_model];
1477 [defaultCenter addObserver:self
1478 selector:@selector(tabDeselected:)
1479 name:kTabModelTabDeselectedNotification
1480 object:_model];
1481 [defaultCenter addObserver:self
1482 selector:@selector(tabWasAdded:)
1483 name:kTabModelNewTabWillOpenNotification
1484 object:_model];
1485}
1486
1487- (void)pageLoadStarting:(NSNotification*)notify {
1488 Tab* tab = notify.userInfo[kTabModelTabKey];
1489 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511490
1491 // Stop any Find in Page searches and close the find bar when navigating to a
1492 // new page.
1493 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361494
sdefresnee65fd872016-12-19 13:38:131495 if (tab == [_model currentTab]) {
1496 // TODO(pinkerton): Fill in here about hiding the forward button on
1497 // navigation.
1498 }
1499}
1500
1501- (void)pageLoadStarted:(NSNotification*)notify {
1502 Tab* tab = notify.userInfo[kTabModelTabKey];
1503 DCHECK(tab);
1504 if (tab == [_model currentTab]) {
1505 if (![self isTabNativePage:tab]) {
1506 [_toolbarController currentPageLoadStarted];
1507 }
1508 [self updateVoiceSearchBarVisibilityAnimated:NO];
1509 }
1510}
1511
1512- (void)pageLoadComplete:(NSNotification*)notify {
1513 // Update the UI, but only if the current tab.
1514 Tab* tab = notify.userInfo[kTabModelTabKey];
1515 if (tab == [_model currentTab]) {
1516 // There isn't any need to update the toolbar here. When the page finishes,
1517 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1518 }
1519
1520 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1521
1522 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1523}
1524
1525- (void)tabDeselected:(NSNotification*)notify {
1526 DCHECK(notify);
1527 Tab* tab = notify.userInfo[kTabModelTabKey];
1528 DCHECK(tab);
1529 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221530 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131531}
1532
1533- (void)tabWasAdded:(NSNotification*)notify {
1534 Tab* tab = notify.userInfo[kTabModelTabKey];
1535 DCHECK(tab);
1536
1537 // Update map if a native controller was vended before the tab was added.
1538 id<CRWNativeContent> nativeController =
1539 [_nativeControllersForTabIDs objectForKey:kNativeControllerTemporaryKey];
1540 if (nativeController) {
1541 [_nativeControllersForTabIDs
1542 removeObjectForKey:kNativeControllerTemporaryKey];
1543 [_nativeControllersForTabIDs setObject:nativeController forKey:tab.tabId];
1544 }
1545
1546 // When adding new tabs, check what kind of reminder infobar should
1547 // be added to the new tab. Try to add only one of them.
1548 // This check is done when a new tab is added either through the Tools Menu
1549 // "New Tab" or through "New Tab" in Stack View Controller. This method
1550 // is called after a new tab has added and finished initial navigation.
1551 // If this is added earlier, the initial navigation may end up clearing
1552 // the infobar(s) that are just added. See http://crbug/340250 for details.
1553 [[UpgradeCenter sharedInstance] addInfoBarToManager:[tab infoBarManager]
1554 forTabId:[tab tabId]];
1555 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1556 ios_internal::sync::displaySyncErrors(_browserState, tab);
1557 }
1558
1559 // The rest of this function initiates the new tab animation, which is
1560 // phone-specific.
1561 if (IsIPadIdiom())
1562 return;
1563
1564 // Do nothing if browsing is currently suspended. The BVC will set everything
1565 // up correctly when browsing resumes.
1566 if (!self.visible || ![_model webUsageEnabled])
1567 return;
1568
1569 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1570
1571 // Block that starts voice search at the end of new Tab animation if
1572 // necessary.
1573 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1574 if (_startVoiceSearchAfterNewTabAnimation) {
1575 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001576 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131577 }
1578 };
1579
kkhorimotoa44349c12017-04-12 23:02:121580 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131581 if (!inBackground) {
1582 UIView* animationParentView = _contentArea;
1583 // Create the new page image, and load with the new tab page snapshot.
1584 CGFloat newPageOffset = 0;
1585 UIImageView* newPage;
kkhorimotob110b262017-06-01 18:38:251586 if (tab.lastCommittedURL == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
sdefresnee65fd872016-12-19 13:38:131587 !IsIPadIdiom()) {
1588 animationParentView = self.view;
1589 newPage = [self pageFullScreenOpenCloseAnimationView];
1590 } else {
1591 newPage = [self pageOpenCloseAnimationView];
1592 }
1593 newPageOffset = newPage.frame.origin.y;
1594
1595 [tab view].frame = _contentArea.bounds;
1596 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1597 [animationParentView addSubview:newPage];
1598 CGPoint origin = [self lastTapPoint];
1599 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1600 newPage, -newPageOffset,
1601 newPage.frame.size.height - newPage.image.size.height, origin,
1602 _isOffTheRecord, NULL, ^{
1603 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121604 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421605 // Use the model's currentTab here because it is possible that it can
1606 // be reset to a new value before the new Tab animation finished (e.g.
1607 // if another Tab shows a dialog via |dialogPresenter|). However, that
1608 // tab's view hasn't been displayed yet because it was in a new tab
1609 // animation.
1610 Tab* currentTab = [_model currentTab];
1611 if (currentTab) {
1612 [self tabSelected:currentTab];
1613 }
sdefresnee65fd872016-12-19 13:38:131614 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391615
1616 if (self.foregroundTabWasAddedCompletionBlock) {
1617 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451618 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391619 }
sdefresnee65fd872016-12-19 13:38:131620 });
1621 } else {
1622 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1623 // snapshot before adding the views needed for the background animation.
1624 Tab* topTab = [_model currentTab];
1625 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1626 visibleFrameOnly:self.isToolbarOnScreen];
1627 // Add three layers in order on top of the contentArea for the animation:
1628 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271629 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131630 InstallBackgroundInView(background);
1631 [_contentArea addSubview:background];
1632
1633 // 2. A CardView displaying the data from the current tab.
1634 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1635 NSString* title = [topTab title];
1636 if (![title length])
1637 title = [topTab urlDisplayString];
1638 [topCard setTitle:title];
1639 [topCard setFavicon:[topTab favicon]];
1640 [topCard setImage:image];
1641
1642 // 3. A new, blank CardView to represent the new tab being added.
1643 // Launch the new background tab animation.
1644 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1645 topCard, [_contentArea frame], IsPortrait(), ^{
1646 [background removeFromSuperview];
1647 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121648 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131649 // Resnapshot the top card if it has its own toolbar, as the toolbar
1650 // will be captured in the new tab animation, but isn't desired for
1651 // the stack view snapshots.
1652 id nativeController = [self nativeControllerForTab:topTab];
1653 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1654 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1655 startVoiceSearchIfNecessaryBlock();
1656 });
peterlaurens9f1b6e02017-06-22 17:46:451657 // Reset the foreground tab completion block so that it can never be
1658 // called more than once regardless of foreground/background tab
1659 // appearances.
1660 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131661 }
1662}
1663
1664#pragma mark - UI Configuration and Layout
1665
1666- (void)updateWithTabModel:(TabModel*)model
1667 browserState:(ios::ChromeBrowserState*)browserState {
1668 DCHECK(model);
1669 DCHECK(browserState);
1670 DCHECK(!_model);
1671 DCHECK(!_browserState);
1672 _browserState = browserState;
1673 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271674 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381675
sdefresnee65fd872016-12-19 13:38:131676 [_model addObserver:self];
1677
1678 if (!_isOffTheRecord) {
1679 [DefaultIOSWebViewFactory
1680 registerWebViewFactory:[ChromeWebViewFactory class]];
1681 }
1682 NSUInteger count = [_model count];
1683 for (NSUInteger index = 0; index < count; ++index)
1684 [self installDelegatesForTab:[_model tabAtIndex:index]];
1685
1686 [self registerForNotifications];
1687
gambardbdc07cc2017-02-03 16:43:111688 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
1689 _browserState->GetRequestContext(), web::WebThread::GetBlockingPool());
stkhapuginc9eee7b2017-04-10 15:49:271690 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131691
sdefresnedc432f42017-01-17 14:36:591692 // Register for bookmark changed notification (BookmarkModel may be null
1693 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131694 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591695 if (_bookmarkModel) {
1696 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1697 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1698 }
sdefresnee65fd872016-12-19 13:38:131699}
1700
sdefresnee65fd872016-12-19 13:38:131701- (void)browserStateDestroyed {
1702 [self setActive:NO];
1703 // Reset the toolbar opacity in case it was changed for contextual search.
1704 [self updateToolbarControlsAlpha:1.0];
1705 [self updateToolbarBackgroundAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:131706 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271707 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131708 [_toolbarController browserStateDestroyed];
1709 [_model browserStateDestroyed];
michaeldobc2f42e2017-01-12 19:04:471710 [_preloadController browserStateDestroyed];
stkhapuginc9eee7b2017-04-10 15:49:271711 _preloadController = nil;
sdefresnee65fd872016-12-19 13:38:131712 // The file remover needs the browser state, so needs to be destroyed now.
stkhapuginc9eee7b2017-04-10 15:49:271713 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131714 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391715 [_dispatcher stopDispatchingToTarget:self];
1716 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131717}
1718
1719- (void)installFakeStatusBar {
1720 if (IsIPadIdiom()) {
1721 CGFloat statusBarHeight = StatusBarHeight();
1722 CGRect statusBarFrame =
1723 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271724 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131725 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1726 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1727 [statusBarView layer].zPosition = 99;
1728 [[self view] addSubview:statusBarView];
1729 }
1730
1731 // Add a white bar on phone so that the status bar on the NTP is white.
1732 if (!IsIPadIdiom()) {
1733 CGFloat statusBarHeight = StatusBarHeight();
1734 CGRect statusBarFrame =
1735 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271736 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131737 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1738 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1739 [self.view insertSubview:statusBarView atIndex:0];
1740 }
1741}
1742
1743// Create the UI elements. May or may not have valid browser state & tab model.
1744- (void)buildToolbarAndTabStrip {
1745 DCHECK([self isViewLoaded]);
1746 DCHECK(!_toolbarModelDelegate);
1747
1748 // Create the preload controller before the toolbar controller.
1749 if (!_preloadController) {
stkhapuginc9eee7b2017-04-10 15:49:271750 _preloadController = [_dependencyFactory newPreloadController];
sdefresnee65fd872016-12-19 13:38:131751 [_preloadController setDelegate:self];
1752 }
1753
1754 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201755 _toolbarModelDelegate.reset(
1756 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131757 _toolbarModelIOS.reset([_dependencyFactory
1758 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Mark Cogan6ebbde02017-07-07 12:50:131759 _toolbarController =
1760 [_dependencyFactory newWebToolbarControllerWithDelegate:self
1761 urlLoader:self
1762 preloadProvider:_preloadController
1763 dispatcher:self.dispatcher];
justincohen75011c32017-04-28 16:31:391764 [_dispatcher startDispatchingToTarget:_toolbarController
1765 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131766 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271767 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131768 _voiceSearchController->SetDelegate(_toolbarController);
1769
1770 // If needed, create the tabstrip.
1771 if (IsIPadIdiom()) {
stkhapuginc9eee7b2017-04-10 15:49:271772 _tabStripController =
Mark Cogandfcdea72017-07-18 13:47:381773 [_dependencyFactory newTabStripControllerWithTabModel:_model
1774 dispatcher:self.dispatcher];
stkhapuginc9eee7b2017-04-10 15:49:271775 _tabStripController.fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131776 }
1777
1778 // Create infobar container.
1779 if (!_infoBarContainerDelegate) {
1780 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1781 _infoBarContainer.reset(
1782 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1783 }
1784}
1785
1786// Enable functionality that only makes sense if the views are loaded and
1787// both browser state and tab model are valid.
1788- (void)addUIFunctionalityForModelAndBrowserState {
1789 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051790 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131791 DCHECK(_model);
1792 DCHECK([self isViewLoaded]);
1793
1794 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1795
1796 infobars::InfoBarManager* infoBarManager =
1797 [[_model currentTab] infoBarManager];
1798 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1799
mathp9b4c11d2017-07-06 20:24:131800 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:271801 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131802 initWithBaseViewController:self
stkhapuginc9eee7b2017-04-10 15:49:271803 browserState:_browserState];
Randall Raymond8b66a402017-06-09 14:19:051804 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:191805 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:131806 }
1807}
1808
1809// Set the frame for the various views. View must be loaded.
1810- (void)setUpViewLayout {
1811 DCHECK([self isViewLoaded]);
1812
1813 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1814
1815 CGFloat minY = [self headerOffset];
1816
1817 // If needed, position the tabstrip.
1818 if (IsIPadIdiom()) {
1819 [self layoutTabStripForWidth:widthOfView];
1820 [[self view] addSubview:[_tabStripController view]];
1821 minY += CGRectGetHeight([[_tabStripController view] frame]);
1822 }
1823
1824 // Position the toolbar next, either at the top of the browser view or
1825 // directly under the tabstrip.
1826 CGRect toolbarFrame = [[_toolbarController view] frame];
1827 toolbarFrame.origin = CGPointMake(0, minY);
1828 toolbarFrame.size.width = widthOfView;
1829 [[_toolbarController view] setFrame:toolbarFrame];
1830
1831 // Place the infobar container above the content area.
1832 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1833 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1834
1835 // Place the toolbar controller above the infobar container.
1836 [[self view] insertSubview:[_toolbarController view]
1837 aboveSubview:infoBarContainerView];
1838 minY += CGRectGetHeight(toolbarFrame);
1839
1840 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1841 // content slightly.
1842 minY -= [ToolbarController toolbarDropShadowHeight];
1843
1844 // Adjust the content area to be under the toolbar, for fullscreen or below
1845 // the toolbar is not fullscreen.
1846 CGRect contentFrame = [_contentArea frame];
1847 CGFloat marginWithHeader = StatusBarHeight();
1848 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1849 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1850 contentFrame.origin.y = overlap;
1851 [_contentArea setFrame:contentFrame];
1852
1853 // Adjust the infobar container to be either at the bottom of the screen
1854 // (iPhone) or on the lower toolbar edge (iPad).
1855 CGRect infoBarFrame = contentFrame;
1856 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1857 infoBarFrame.size.height = 0;
1858 [infoBarContainerView setFrame:infoBarFrame];
1859
1860 // Attach the typing shield to the content area but have it hidden.
1861 [_typingShield setFrame:[_contentArea frame]];
1862 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1863 [_typingShield setHidden:YES];
1864 _typingShield.accessibilityIdentifier = @"Typing Shield";
1865 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1866}
1867
1868- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1869 UIView* tabStripView = [_tabStripController view];
1870 CGRect tabStripFrame = [tabStripView frame];
1871 tabStripFrame.origin = CGPointZero;
1872 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1873 // because the CGPointZero above will break reset the offset, but it's not
1874 // clear what removing that will do.
1875 tabStripFrame.origin.y = [self headerOffset];
1876 tabStripFrame.size.width = maxWidth;
1877 [tabStripView setFrame:tabStripFrame];
1878}
1879
1880- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1881 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:441882 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131883
kkhorimotoa44349c12017-04-12 23:02:121884 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:131885 // Hide findbar. |updateToolbar| will restore the findbar later.
1886 [self hideFindBarWithAnimation:NO];
1887
1888 // Make new content visible, resizing it first as the orientation may
1889 // have changed from the last time it was displayed.
1890 [[tab view] setFrame:_contentArea.bounds];
1891 [_contentArea displayContentView:[tab view]];
1892 }
1893 [self updateToolbar];
1894
1895 if (newSelection)
1896 [_toolbarController selectedTabChanged];
1897
1898 // Notify the Tab that it was displayed.
1899 [tab wasShown];
1900}
1901
1902- (void)initializeBookmarkInteractionController {
1903 if (_bookmarkInteractionController)
1904 return;
stkhapuginc9eee7b2017-04-10 15:49:271905 _bookmarkInteractionController =
1906 [[BookmarkInteractionController alloc] initWithBrowserState:_browserState
1907 loader:self
1908 parentController:self];
sdefresnee65fd872016-12-19 13:38:131909}
1910
1911// Update the state of back and forward buttons, hiding the forward button if
1912// there is nowhere to go. Assumes the model's current tab is up to date.
1913- (void)updateToolbar {
1914 // If the BVC has been partially torn down for low memory, wait for the
1915 // view rebuild to handle toolbar updates.
1916 if (!(_toolbarModelIOS && _browserState))
1917 return;
1918
1919 Tab* tab = [_model currentTab];
1920 if (![tab navigationManager])
1921 return;
1922 [_toolbarController updateToolbarState];
1923 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1924
1925 if (tab.isPrerenderTab && !_toolbarModelIOS->IsLoading())
1926 [_toolbarController showPrerenderingAnimation];
1927
1928 // Also update the loading state for the tools menu (that is really an
1929 // extension of the toolbar on the iPhone).
1930 if (!IsIPadIdiom())
1931 [[_toolbarController toolsPopupController]
1932 setIsTabLoading:_toolbarModelIOS->IsLoading()];
1933
rohitrao005a6432017-03-16 20:52:421934 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
1935 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:131936 [self showFindBarWithAnimation:NO
1937 selectText:YES
1938 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:361939 }
sdefresnee65fd872016-12-19 13:38:131940
1941 // Hide the toolbar if displaying phone NTP.
1942 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:551943 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:131944 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:551945 if (item) {
1946 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:131947 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
1948 hideToolbar = isNTP && !_isOffTheRecord &&
1949 ![_toolbarController isOmniboxFirstResponder] &&
1950 ![_toolbarController showingOmniboxPopup];
1951 }
1952 [[_toolbarController view] setHidden:hideToolbar];
1953 }
1954}
1955
1956- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:121957 self.dialogPresenter.active =
1958 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:131959}
1960
1961- (void)dismissPopups {
jif7fed8122017-02-08 13:15:251962 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:131963 [self hidePageInfoPopupForView:nil];
1964 [_toolbarController dismissTabHistoryPopup];
sdefresnee65fd872016-12-19 13:38:131965}
1966
1967#pragma mark - Tap handling
1968
Mark Cogandfcdea72017-07-18 13:47:381969- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:031970 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
1971 _lastTapPoint = CGPointZero;
1972 } else {
1973 _lastTapPoint =
1974 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:131975 }
Mark Cogane01ebce2017-07-12 19:31:031976 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:131977}
1978
1979- (CGPoint)lastTapPoint {
1980 if (CACurrentMediaTime() - _lastTapTime < 1) {
1981 return _lastTapPoint;
1982 }
1983 return CGPointZero;
1984}
1985
1986- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
1987 UIView* view = gestureRecognizer.view;
1988 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
1989 _lastTapPoint =
1990 [[view superview] convertPoint:viewCoordinate toView:self.view];
1991 _lastTapTime = CACurrentMediaTime();
1992}
1993
1994- (BOOL)addTabIfNoTabWithNormalBrowserState {
1995 if (![_model count]) {
1996 if (!_isOffTheRecord) {
1997 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1998 transition:ui::PAGE_TRANSITION_TYPED];
1999 return YES;
2000 }
2001 }
2002 return NO;
2003}
2004
2005#pragma mark - Tab creation and selection
2006
2007// Called when either a tab finishes loading or when a tab with finished content
2008// is added directly to the model via pre-rendering.
2009- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2010 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2011
2012 // Persist the session on a delay.
2013 [_model saveSessionImmediately:NO];
2014}
2015
2016- (Tab*)addSelectedTabWithURL:(const GURL&)url
2017 postData:(TemplateURLRef::PostContent*)postData
2018 transition:(ui::PageTransition)transition {
2019 return [self addSelectedTabWithURL:url
2020 postData:postData
2021 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492022 transition:transition
2023 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132024}
2025
2026- (Tab*)addSelectedTabWithURL:(const GURL&)url
2027 transition:(ui::PageTransition)transition {
2028 return [self addSelectedTabWithURL:url
2029 atIndex:[_model count]
2030 transition:transition];
2031}
2032
2033- (Tab*)addSelectedTabWithURL:(const GURL&)url
2034 atIndex:(NSUInteger)position
2035 transition:(ui::PageTransition)transition {
2036 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492037 atIndex:position
2038 transition:transition
2039 tabAddedCompletion:nil];
2040}
2041
2042- (Tab*)addSelectedTabWithURL:(const GURL&)url
2043 atIndex:(NSUInteger)position
2044 transition:(ui::PageTransition)transition
2045 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2046 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132047 postData:NULL
2048 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492049 transition:transition
2050 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132051}
2052
2053- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2054 postData:(TemplateURLRef::PostContent*)postData
2055 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492056 transition:(ui::PageTransition)transition
2057 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132058 if (position == NSNotFound)
2059 position = [_model count];
2060 DCHECK(position <= [_model count]);
2061
2062 web::NavigationManager::WebLoadParams params(URL);
2063 params.transition_type = transition;
2064 if (postData) {
2065 // Extract the content type and post params from |postData| and add them
2066 // to the load params.
2067 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2068 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2069 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172070 params.post_data.reset(data);
2071 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132072 }
Olivier Robind508a5632017-07-19 16:29:492073
2074 if (tabAddedCompletion) {
2075 if (self.foregroundTabWasAddedCompletionBlock) {
2076 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2077 self.foregroundTabWasAddedCompletionBlock;
2078 self.foregroundTabWasAddedCompletionBlock = ^{
2079 oldForegroundTabWasAddedCompletionBlock();
2080 tabAddedCompletion();
2081 };
2082 } else {
2083 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2084 }
2085 }
2086
sdefresnea6395912017-03-01 01:14:352087 Tab* tab = [_model insertTabWithLoadParams:params
2088 opener:nil
2089 openedByDOM:NO
2090 atIndex:position
2091 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132092 return tab;
2093}
2094
olivierrobin889af53f2017-03-01 14:56:322095// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132096- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322097 web::WebState* webState = tab.webState;
2098 if (!webState)
2099 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392100 web::NavigationItem* visibleItem =
2101 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322102 if (!visibleItem)
2103 return NO;
2104 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132105}
2106
2107- (void)expectNewForegroundTab {
2108 _expectingForegroundTab = YES;
2109}
2110
2111- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2112 CGRect viewBounds, remainder;
2113 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2114 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172115 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132116}
2117
2118- (UIImageView*)pageOpenCloseAnimationView {
2119 CGRect frame = [_contentArea bounds];
2120
2121 frame.size.height = frame.size.height - [self headerHeight];
2122 frame.origin.y = [self headerHeight];
2123
stkhapuginf58b10d02017-04-10 13:36:172124 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132125 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2126 pageView.center = center;
2127
2128 pageView.backgroundColor = [UIColor whiteColor];
2129 return pageView;
2130}
2131
2132- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142133 // Unregistration happens when the Tab is removed from the TabModel.
Mark Cogandfcdea72017-07-18 13:47:382134 tab.dispatcher = self.dispatcher;
sdefresnee65fd872016-12-19 13:38:132135 tab.dialogDelegate = self;
2136 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132137 tab.passKitDialogProvider = self;
2138 tab.fullScreenControllerDelegate = self;
2139 if (!IsIPadIdiom()) {
2140 tab.overscrollActionsControllerDelegate = self;
2141 }
olivierrobin9ce77b82017-01-12 17:29:192142 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132143 tab.tabSnapshottingDelegate = self;
2144 // Install the proper CRWWebController delegates.
2145 tab.webController.nativeProvider = self;
2146 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512147 // BrowserViewController presents SKStoreKitViewController on behalf of a
2148 // tab.
2149 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2150 if (tabHelper)
2151 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132152 tab.webState->SetDelegate(_webStateDelegate.get());
2153}
2154
sdefresne49cf2862017-03-15 13:46:142155- (void)uninstallDelegatesForTab:(Tab*)tab {
Mark Cogandfcdea72017-07-18 13:47:382156 tab.dispatcher = nil;
sdefresne49cf2862017-03-15 13:46:142157 tab.dialogDelegate = nil;
2158 tab.snapshotOverlayProvider = nil;
2159 tab.passKitDialogProvider = nil;
2160 tab.fullScreenControllerDelegate = nil;
2161 if (!IsIPadIdiom()) {
2162 tab.overscrollActionsControllerDelegate = nil;
2163 }
2164 tab.tabHeadersDelegate = nil;
2165 tab.tabSnapshottingDelegate = nil;
2166 tab.webController.nativeProvider = nil;
2167 tab.webController.swipeRecognizerProvider = nil;
2168 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2169 if (tabHelper)
2170 tabHelper->SetLauncher(nil);
2171 tab.webState->SetDelegate(nullptr);
2172}
2173
sdefresnee65fd872016-12-19 13:38:132174// Called when a tab is selected in the model. Make any required view changes.
2175// The notification will not be sent when the tab is already the selected tab.
2176- (void)tabSelected:(Tab*)tab {
2177 DCHECK(tab);
2178
2179 // Ignore changes while the tab stack view is visible (or while suspended).
2180 // The display will be refreshed when this view becomes active again.
2181 if (!self.visible || ![_model webUsageEnabled])
2182 return;
2183
2184 [self displayTab:tab isNewSelection:YES];
2185
kkhorimotoa44349c12017-04-12 23:02:122186 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132187 // Now that the new tab has been displayed, return to normal. Rather than
2188 // keep a reference to the previous tab, just turn off preview mode for all
2189 // tabs (since doing so is a no-op for the tabs that don't have it set).
2190 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272191 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132192 [tab.webController setOverlayPreviewMode:NO];
2193 }
2194 }
2195}
2196
2197#pragma mark - External files
2198
2199- (NSSet*)referencedExternalFiles {
2200 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2201
2202 // TODO(noyau): this is incorrect, the caller should know that the model is
2203 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592204 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132205 return filesReferencedByTabs;
2206
2207 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2208 _bookmarkModel->GetBookmarks(&bookmarks);
2209 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2210 for (const auto& bookmark : bookmarks) {
2211 GURL bookmarkUrl = bookmark.url;
2212 if (UrlIsExternalFileReference(bookmarkUrl)) {
2213 [bookmarkedFiles
2214 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2215 }
2216 }
2217 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2218}
2219
2220- (void)removeExternalFilesImmediately:(BOOL)immediately
2221 completionHandler:(ProceduralBlock)completionHandler {
2222 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2223 DCHECK(!_isOffTheRecord);
2224 _externalFileRemover.reset(new ExternalFileRemover(self));
2225 // Delay the cleanup of the unreferenced files received from other apps
2226 // to not impact startup performance.
2227 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2228 _externalFileRemover->RemoveAfterDelay(
2229 base::TimeDelta::FromSeconds(delay),
stkhapuginf58b10d02017-04-10 13:36:172230 base::BindBlockArc(completionHandler ? completionHandler
2231 : ^{
2232 }));
sdefresnee65fd872016-12-19 13:38:132233}
2234
Sylvain Defresne41170aa2017-06-15 10:25:202235- (void)shutdown {
2236 DCHECK(!_isShutdown);
2237 _isShutdown = YES;
2238
2239 _tabStripController = nil;
2240 _infoBarContainer = nil;
2241 _readingListMenuNotifier = nil;
2242 if (_bookmarkModel)
2243 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2244 [_model removeObserver:self];
2245 [[UpgradeCenter sharedInstance] unregisterClient:self];
2246 [[NSNotificationCenter defaultCenter] removeObserver:self];
2247 [_toolbarController setDelegate:nil];
2248 if (_voiceSearchController)
2249 _voiceSearchController->SetDelegate(nil);
2250 [_rateThisAppDialog setDelegate:nil];
2251 [_model closeAllTabs];
2252}
2253
sdefresnee65fd872016-12-19 13:38:132254#pragma mark - SnapshotOverlayProvider methods
2255
2256- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2257 NSMutableArray* overlays = [NSMutableArray array];
2258 if (![_model webUsageEnabled]) {
2259 return overlays;
2260 }
2261 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2262 if (voiceSearchView) {
2263 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272264 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132265 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272266 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132267 [overlays addObject:voiceSearchOverlay];
2268 }
2269 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2270 if (infoBarView) {
2271 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272272 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132273 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272274 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132275 [overlays addObject:infoBarOverlay];
2276 }
2277 return overlays;
2278}
2279
2280#pragma mark -
2281
2282- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2283 if (IsIPadIdiom()) {
2284 // Not using overlays on iPad because the content is pushed down by
2285 // infobar and the transition between snapshot and fresh page can
2286 // cause both snapshot and real infobars to appear at the same time.
2287 return nil;
2288 }
2289 Tab* currentTab = [_model currentTab];
2290 if (tab && tab == currentTab) {
2291 infobars::InfoBarManager* infoBarManager = [currentTab infoBarManager];
2292 if (infoBarManager->infobar_count() > 0) {
2293 DCHECK(_infoBarContainer);
2294 return _infoBarContainer->view();
2295 }
2296 }
2297 return nil;
2298}
2299
2300- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272301 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132302 // There is no UI representation for non-current tabs or there is
2303 // no _infoBarContainer instantiated yet.
2304 // Return offset outside of tab.
2305 return CGRectGetMaxY(self.view.frame);
2306 } else if (IsIPadIdiom()) {
2307 // The infobars on iPad are display at the top of a tab.
2308 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2309 } else {
2310 // The infobars on iPhone are displayed at the bottom of a tab.
2311 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2312 return CGRectGetMaxY(visibleFrame) -
2313 CGRectGetHeight(_infoBarContainer->view().frame);
2314 }
2315}
2316
2317- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2318 Tab* currentTab = [_model currentTab];
2319 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2320 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2321 return _voiceSearchBar;
2322 }
2323 return nil;
2324}
2325
2326- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2327 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2328 // There is no UI representation for non-current tabs or there is
2329 // no visible voice search. Return offset outside of tab.
2330 return CGRectGetMaxY(self.view.frame);
2331 } else {
2332 // The voice search bar on iPhone is displayed at the bottom of a tab.
2333 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2334 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2335 }
2336}
2337
2338- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272339 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132340 VoiceSearchProvider* provider =
2341 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2342 if (provider) {
2343 _voiceSearchController =
2344 provider->CreateVoiceSearchController(_browserState);
2345 _voiceSearchController->SetDelegate(_toolbarController);
2346 }
2347 }
2348}
2349
2350- (void)ensureVoiceSearchBarCreated {
2351 if (_voiceSearchBar)
2352 return;
2353
2354 CGFloat width = CGRectGetWidth([[self view] bounds]);
2355 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2356 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272357 _voiceSearchBar = ios::GetChromeBrowserProvider()
2358 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002359 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132360 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2361 [_voiceSearchBar setHidden:YES];
2362 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2363 UIViewAutoresizingFlexibleWidth];
2364 [self.view insertSubview:_voiceSearchBar
2365 belowSubview:_infoBarContainer->view()];
2366}
2367
2368- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2369 // Voice search bar exists and is shown/hidden.
2370 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272371 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132372 return;
2373
2374 // Voice search bar doesn't exist and thus is not visible.
2375 if (!_voiceSearchBar && !show)
2376 return;
2377
2378 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272379 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132380 else
stkhapuginc9eee7b2017-04-10 15:49:272381 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132382}
2383
2384- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2385 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2386 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2387 self.shouldShowVoiceSearchBar) {
2388 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2389 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272390 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132391 }
2392 id currentNativeController =
2393 [self nativeControllerForTab:self.tabModel.currentTab];
2394 Protocol* possibleOwnerProtocol =
2395 @protocol(LogoAnimationControllerOwnerOwner);
2396 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2397 [currentNativeController logoAnimationControllerOwner]) {
2398 // If the current native controller is showing a GLIF view (e.g. the NTP
2399 // when there is no doodle), use that GLIFControllerOwner.
2400 return [currentNativeController logoAnimationControllerOwner];
2401 }
2402 return nil;
2403}
2404
2405#pragma mark - PassKitDialogProvider methods
2406
2407- (void)presentPassKitDialog:(NSData*)data {
2408 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272409 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132410 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272411 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132412 if (error || !data) {
2413 if ([_model currentTab]) {
2414 infobars::InfoBarManager* infoBarManager =
2415 [[_model currentTab] infoBarManager];
2416 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2417 // NULL, replace if with DCHECK).
2418 if (infoBarManager)
2419 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2420 }
2421 } else {
2422 PKAddPassesViewController* passKitViewController =
2423 [_dependencyFactory newPassKitViewControllerForPass:pass];
2424 if (passKitViewController) {
2425 [self presentViewController:passKitViewController
2426 animated:YES
2427 completion:^{
2428 }];
2429 }
2430 }
2431}
2432
2433- (UIStatusBarStyle)preferredStatusBarStyle {
2434 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2435 : UIStatusBarStyleDefault;
2436}
2437
2438#pragma mark - CRWWebStateDelegate methods.
2439
eugenebut75a06fa72017-01-09 17:09:552440- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512441 createNewWebStateForURL:(const GURL&)URL
2442 openerURL:(const GURL&)openerURL
2443 initiatedByUser:(BOOL)initiatedByUser {
2444 // Check if requested web state is a popup and block it if necessary.
2445 if (!initiatedByUser) {
2446 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2447 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102448 // It's possible for a page to inject a popup into a window created via
2449 // window.open before its initial load is committed. Rather than relying
2450 // on the last committed or pending NavigationItem's referrer policy, just
2451 // use ReferrerPolicyDefault.
2452 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2453 // policy once referrer policies are correctly recorded in
2454 // NavigationItems.
2455 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512456 helper->HandlePopup(URL, referrer);
2457 return nil;
2458 }
2459 }
2460
2461 // Requested web state should not be blocked from opening.
2462 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2463 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2464
2465 // Tabs open by DOM are always renderer initiated.
2466 web::NavigationManager::WebLoadParams params(GURL{});
2467 params.transition_type = ui::PAGE_TRANSITION_LINK;
2468 params.is_renderer_initiated = true;
2469 Tab* childTab = [[self tabModel]
2470 insertTabWithLoadParams:params
2471 opener:currentTab
2472 openedByDOM:YES
2473 atIndex:TabModelConstants::kTabPositionAutomatically
2474 inBackground:NO];
2475 return childTab.webState;
2476}
2477
eugenebutb46b2122017-03-14 02:43:262478- (void)closeWebState:(web::WebState*)webState {
2479 // Only allow a web page to close itself if it was opened by DOM, or if there
2480 // are no navigation items.
2481 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582482 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262483
2484 if (![self tabModel])
2485 return;
2486
2487 NSUInteger index = [[self tabModel] indexOfTab:tab];
2488 if (index != NSNotFound)
2489 [[self tabModel] closeTabAtIndex:index];
2490}
2491
eugenebut275f5892017-03-09 22:20:512492- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552493 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2494 switch (params.disposition) {
2495 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2496 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2497 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352498 insertTabWithURL:params.url
2499 referrer:params.referrer
2500 transition:params.transition
2501 opener:LegacyTabHelper::GetTabForWebState(webState)
2502 openedByDOM:NO
2503 atIndex:TabModelConstants::kTabPositionAutomatically
2504 inBackground:(params.disposition ==
2505 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552506 return tab.webState;
2507 }
2508 case WindowOpenDisposition::CURRENT_TAB: {
2509 web::NavigationManager::WebLoadParams loadParams(params.url);
2510 loadParams.referrer = params.referrer;
2511 loadParams.transition_type = params.transition;
2512 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2513 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2514 return webState;
2515 }
eugenebutd0984e82017-02-22 23:47:512516 case WindowOpenDisposition::NEW_POPUP: {
2517 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352518 insertTabWithURL:params.url
2519 referrer:params.referrer
2520 transition:params.transition
2521 opener:LegacyTabHelper::GetTabForWebState(webState)
2522 openedByDOM:YES
2523 atIndex:TabModelConstants::kTabPositionAutomatically
2524 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512525 return tab.webState;
2526 }
eugenebut75a06fa72017-01-09 17:09:552527 default:
2528 NOTIMPLEMENTED();
2529 return nullptr;
2530 };
2531}
2532
sdefresnee65fd872016-12-19 13:38:132533- (BOOL)webState:(web::WebState*)webState
2534 handleContextMenu:(const web::ContextMenuParams&)params {
2535 // Prevent context menu from displaying for a tab which is no longer the
2536 // current one.
2537 if (webState != [_model currentTab].webState) {
2538 return NO;
2539 }
2540
2541 // No custom context menu if no valid url is available in |params|.
2542 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
2543 return NO;
2544 }
2545
2546 DCHECK(_browserState);
2547 DCHECK([_model currentTab]);
2548
stkhapuginc9eee7b2017-04-10 15:49:272549 _contextMenuCoordinator =
2550 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2551 params:params];
sdefresnee65fd872016-12-19 13:38:132552
2553 NSString* title = nil;
2554 ProceduralBlock action = nil;
2555
stkhapuginc9eee7b2017-04-10 15:49:272556 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132557 GURL link = params.link_url;
2558 bool isLink = link.is_valid();
2559 GURL imageUrl = params.src_url;
2560 bool isImage = imageUrl.is_valid();
kkhorimotob110b262017-06-01 18:38:252561 const GURL& committedURL = [_model currentTab].lastCommittedURL;
sdefresnee65fd872016-12-19 13:38:132562
2563 if (isLink) {
2564 if (link.SchemeIs(url::kJavaScriptScheme)) {
2565 // Open
2566 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2567 action = ^{
2568 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2569 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2570 };
2571 [_contextMenuCoordinator addItemWithTitle:title action:action];
2572 }
2573
2574 if (web::UrlHasWebScheme(link)) {
kkhorimotob110b262017-06-01 18:38:252575 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132576
sdefresnee65fd872016-12-19 13:38:132577 // Open in New Tab.
2578 title = l10n_util::GetNSStringWithFixup(
2579 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2580 action = ^{
2581 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002582 // The "New Tab" item in the context menu opens a new tab in the current
2583 // browser state. |isOffTheRecord| indicates whether or not the current
2584 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132585 [weakSelf webPageOrderedOpen:link
2586 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002587 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132588 inBackground:YES
2589 appendTo:kCurrentTab];
2590 };
2591 [_contextMenuCoordinator addItemWithTitle:title action:action];
2592 if (!_isOffTheRecord) {
2593 // Open in Incognito Tab.
2594 title = l10n_util::GetNSStringWithFixup(
2595 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2596 action = ^{
2597 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2598 [weakSelf webPageOrderedOpen:link
2599 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132600 inIncognito:YES
2601 inBackground:NO
2602 appendTo:kCurrentTab];
2603 };
2604 [_contextMenuCoordinator addItemWithTitle:title action:action];
2605 }
olivierrobin51d4cf42017-01-17 13:32:352606 }
gambard65d69152017-03-23 17:44:222607 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352608 NSString* innerText = params.link_text;
2609 if ([innerText length] > 0) {
2610 // Add to reading list.
2611 title = l10n_util::GetNSStringWithFixup(
2612 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2613 action = ^{
2614 Record(ACTION_READ_LATER, isImage, isLink);
2615 [weakSelf addToReadingListURL:link title:innerText];
2616 };
2617 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532618 }
sdefresnee65fd872016-12-19 13:38:132619 }
2620 // Copy Link.
2621 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2622 action = ^{
2623 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282624 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132625 };
2626 [_contextMenuCoordinator addItemWithTitle:title action:action];
2627 }
2628 if (isImage) {
kkhorimotob110b262017-06-01 18:38:252629 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132630 // Save Image.
gambard98b4ddf2017-04-18 07:14:052631 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132632 action = ^{
2633 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2634 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2635 };
2636 [_contextMenuCoordinator addItemWithTitle:title action:action];
2637 // Open Image.
2638 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2639 action = ^{
2640 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2641 [weakSelf loadURL:imageUrl
2642 referrer:referrer
2643 transition:ui::PAGE_TRANSITION_LINK
2644 rendererInitiated:YES];
2645 };
2646 [_contextMenuCoordinator addItemWithTitle:title action:action];
2647 // Open Image In New Tab.
2648 title = l10n_util::GetNSStringWithFixup(
2649 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2650 action = ^{
2651 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2652 [weakSelf webPageOrderedOpen:imageUrl
2653 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132654 inBackground:true
2655 appendTo:kCurrentTab];
2656 };
2657 [_contextMenuCoordinator addItemWithTitle:title action:action];
2658
2659 TemplateURLService* service =
2660 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102661 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132662 if (defaultURL && !defaultURL->image_url().empty() &&
2663 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2664 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2665 defaultURL->short_name());
2666 action = ^{
2667 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2668 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2669 };
2670 [_contextMenuCoordinator addItemWithTitle:title action:action];
2671 }
2672 }
2673
2674 [_contextMenuCoordinator start];
2675 return YES;
2676}
2677
eugenebutb739bdc2017-01-25 06:32:482678- (void)webState:(web::WebState*)webState
2679 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2680 // Display the action sheet with the arrow pointing at the top center of the
2681 // web contents.
sdefresne0452a9d2017-02-09 15:33:282682 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482683 UIView* view = webState->GetView();
2684 CGPoint dialogLocation =
2685 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282686 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032687 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172688 helper->PresentDialog(dialogLocation,
2689 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052690 handler(shouldContinue);
2691 }));
eugenebutb739bdc2017-01-25 06:32:482692}
2693
sdefresnee65fd872016-12-19 13:38:132694- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2695 (web::WebState*)webState {
2696 return _javaScriptDialogPresenter.get();
2697}
2698
eugenebut63232102017-01-19 16:19:402699- (void)webState:(web::WebState*)webState
2700 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2701 proposedCredential:(NSURLCredential*)proposedCredential
2702 completionHandler:(void (^)(NSString* username,
2703 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422704 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2705 if ([tab isPrerenderTab]) {
2706 [tab discardPrerender];
2707 if (handler) {
2708 handler(nil, nil);
2709 }
2710 return;
2711 }
2712
eugenebut63232102017-01-19 16:19:402713 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2714 proposedCredential:proposedCredential
2715 webState:webState
2716 completionHandler:handler];
2717}
2718
sdefresnee65fd872016-12-19 13:38:132719#pragma mark - FullScreenControllerDelegate methods
2720
2721- (CGFloat)headerOffset {
2722 if (IsIPadIdiom())
2723 return StatusBarHeight();
2724 return 0.0;
2725}
2726
stkhapugin952ecef2017-04-11 12:11:452727- (NSArray<HeaderDefinition*>*)headerViews {
2728 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132729 if (![self isViewLoaded])
2730 return results;
2731
2732 if (!IsIPadIdiom()) {
2733 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452734 [results addObject:[HeaderDefinition
2735 definitionWithView:[_toolbarController view]
2736 headerBehaviour:Hideable
2737 heightAdjustment:[ToolbarController
2738 toolbarDropShadowHeight]
2739 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132740 }
2741 } else {
2742 if ([_tabStripController view]) {
stkhapugin952ecef2017-04-11 12:11:452743 [results addObject:[HeaderDefinition
2744 definitionWithView:[_tabStripController view]
2745 headerBehaviour:Hideable
2746 heightAdjustment:0.0
2747 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132748 }
2749 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452750 [results addObject:[HeaderDefinition
2751 definitionWithView:[_toolbarController view]
2752 headerBehaviour:Hideable
2753 heightAdjustment:[ToolbarController
2754 toolbarDropShadowHeight]
2755 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132756 }
2757 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452758 [results addObject:[HeaderDefinition
2759 definitionWithView:[_findBarController view]
2760 headerBehaviour:Overlap
2761 heightAdjustment:0.0
2762 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:132763 }
2764 }
stkhapugin952ecef2017-04-11 12:11:452765 return [results copy];
sdefresnee65fd872016-12-19 13:38:132766}
2767
2768- (UIView*)footerView {
2769 return _voiceSearchBar;
2770}
2771
2772- (CGFloat)headerHeight {
2773 return [self headerHeightForTab:[_model currentTab]];
2774}
2775
2776- (CGFloat)headerHeightForTab:(Tab*)tab {
2777 id nativeController = [self nativeControllerForTab:tab];
2778 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2779 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2780 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2781 // On iPhone, don't add any header height for ToolbarOwner native
2782 // controllers when they're displaying their own toolbar.
2783 return 0;
2784 }
2785
stkhapugin952ecef2017-04-11 12:11:452786 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132787
2788 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452789 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:132790 if (header.view && header.behaviour == Hideable) {
2791 height += CGRectGetHeight([header.view frame]) -
2792 header.heightAdjustement - header.inset;
2793 }
2794 }
2795
2796 return height - StatusBarHeight();
2797}
2798
2799- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:192800 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:132801}
2802
2803- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:452804 NSArray<HeaderDefinition*>* headers = [self headerViews];
2805 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:132806 return 0.0;
2807
2808 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
2809 // API documentation.
2810 if ([[[self tabModel] currentTab] isPrerenderTab])
2811 return [self headerHeight];
2812
2813 UIView* topHeader = headers[0].view;
2814 return -(topHeader.frame.origin.y - [self headerOffset]);
2815}
2816
2817- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
2818 UIView* footer = [self footerView];
2819 CGFloat headerHeight = [self headerHeight];
2820 if (!footer || headerHeight == 0)
2821 return 0.0;
2822
2823 CGFloat footerHeight = CGRectGetHeight(footer.frame);
2824 CGFloat offset = headerOffset * footerHeight / headerHeight;
2825 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
2826}
2827
2828- (void)fullScreenController:(FullScreenController*)controller
2829 headerAnimationCompleted:(BOOL)completed
2830 offset:(CGFloat)offset {
2831 if (completed)
justincohen04c27772016-12-21 20:16:592832 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:132833}
2834
stkhapugin952ecef2017-04-11 12:11:452835- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:132836 atOffset:(CGFloat)headerOffset {
2837 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452838 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:132839 CGRect frame = [header.view frame];
2840 frame.origin.y = height - headerOffset - header.inset;
2841 [header.view setFrame:frame];
2842 if (header.behaviour != Overlap)
2843 height += CGRectGetHeight(frame);
2844 }
2845}
2846
2847- (void)fullScreenController:(FullScreenController*)fullScreenController
2848 drawHeaderViewFromOffset:(CGFloat)headerOffset
2849 animate:(BOOL)animate {
2850 if ([_sideSwipeController inSwipe])
2851 return;
2852
2853 CGRect footerFrame = CGRectZero;
2854 UIView* footer = nil;
2855 // Only animate the voice search bar if the tab is a voice search results tab.
2856 if ([_model currentTab].isVoiceSearchResultsTab) {
2857 footer = [self footerView];
2858 footerFrame = footer.frame;
2859 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2860 }
2861
stkhapugin952ecef2017-04-11 12:11:452862 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132863 void (^block)(void) = ^{
2864 [self setFramesForHeaders:headers atOffset:headerOffset];
2865 footer.frame = footerFrame;
2866 };
2867 void (^completion)(BOOL) = ^(BOOL finished) {
2868 [self fullScreenController:fullScreenController
2869 headerAnimationCompleted:finished
2870 offset:headerOffset];
2871 };
2872 if (animate) {
2873 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2874 delay:0.0
2875 options:UIViewAnimationOptionBeginFromCurrentState
2876 animations:block
2877 completion:completion];
2878 } else {
2879 block();
2880 completion(YES);
2881 }
2882}
2883
2884- (void)fullScreenController:(FullScreenController*)fullScreenController
2885 drawHeaderViewFromOffset:(CGFloat)headerOffset
2886 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
2887 changeTopContentPadding:(BOOL)changeTopContentPadding
2888 scrollingToOffset:(CGFloat)contentOffset {
2889 DCHECK(webViewProxy);
2890 if ([_sideSwipeController inSwipe])
2891 return;
2892
2893 CGRect footerFrame;
2894 UIView* footer = nil;
2895 // Only animate the voice search bar if the tab is a voice search results tab.
2896 if ([_model currentTab].isVoiceSearchResultsTab) {
2897 footer = [self footerView];
2898 footerFrame = footer.frame;
2899 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2900 }
2901
stkhapugin952ecef2017-04-11 12:11:452902 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132903 void (^block)(void) = ^{
2904 [self setFramesForHeaders:headers atOffset:headerOffset];
2905 footer.frame = footerFrame;
2906 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
2907 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
2908 if (changeTopContentPadding)
2909 webViewProxy.topContentPadding = contentOffset;
2910 };
2911 void (^completion)(BOOL) = ^(BOOL finished) {
2912 [self fullScreenController:fullScreenController
2913 headerAnimationCompleted:finished
2914 offset:headerOffset];
2915 };
2916
2917 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2918 delay:0.0
2919 options:UIViewAnimationOptionBeginFromCurrentState
2920 animations:block
2921 completion:completion];
2922}
2923
2924#pragma mark - VoiceSearchBarOwner
2925
2926- (id<VoiceSearchBar>)voiceSearchBar {
2927 return _voiceSearchBar;
2928}
2929
2930#pragma mark - Install OverScrollActionController method.
2931- (void)setOverScrollActionControllerToStaticNativeContent:
2932 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:342933 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:132934 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:172935 [[OverscrollActionsController alloc]
2936 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:132937 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:052938 OverscrollStyle style = _isOffTheRecord
2939 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2940 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:132941 controller.style = style;
2942 nativeContent.overscrollActionsController = controller;
2943 }
2944}
2945
2946#pragma mark - OverscrollActionsControllerDelegate methods.
2947
2948- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:052949 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:132950 switch (action) {
rohitrao922b7111c2017-01-03 14:31:052951 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:382952 [self.dispatcher
2953 openNewTab:[OpenNewTabCommand
2954 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:132955 break;
rohitrao922b7111c2017-01-03 14:31:052956 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:242957 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:132958 break;
liaoyuke563dc4a2017-03-17 18:36:292959 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:132960 if ([[[_model currentTab] webController] loadPhase] ==
2961 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:232962 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:132963 }
liaoyuke563dc4a2017-03-17 18:36:292964
2965 web::WebState* webState = [_model currentTab].webState;
2966 if (webState)
2967 // |check_for_repost| is true because the reload is explicitly initiated
2968 // by the user.
2969 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
2970 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:132971 break;
liaoyuke563dc4a2017-03-17 18:36:292972 }
rohitrao922b7111c2017-01-03 14:31:052973 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:132974 NOTREACHED();
2975 break;
2976 }
2977}
2978
2979- (BOOL)shouldAllowOverscrollActions {
2980 return YES;
2981}
2982
2983- (UIView*)headerView {
2984 return [_toolbarController view];
2985}
2986
2987- (UIView*)toolbarSnapshotView {
2988 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
2989}
2990
2991- (CGFloat)overscrollActionsControllerHeaderInset:
2992 (OverscrollActionsController*)controller {
2993 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
2994 return [self headerHeight];
2995 else
2996 return 0;
2997}
2998
2999- (CGFloat)overscrollHeaderHeight {
3000 return [self headerHeight] + StatusBarHeight();
3001}
3002
3003#pragma mark - TabSnapshottingDelegate methods.
3004
3005- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3006 CGRect pageContentArea = _contentArea.bounds;
3007 if ([_model webUsageEnabled])
3008 pageContentArea = tab.view.bounds;
3009 CGFloat headerHeight = [self headerHeightForTab:tab];
3010 id nativeController = [self nativeControllerForTab:tab];
3011 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3012 headerHeight += [nativeController toolbarHeight];
3013 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3014 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3015}
3016
3017#pragma mark - NewTabPageObserver methods.
3018
3019- (void)selectedPanelDidChange {
3020 [self updateToolbar];
3021}
3022
3023#pragma mark - CRWNativeContentProvider methods
3024
3025- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3026 withError:(NSError*)error
3027 isPost:(BOOL)isPost {
3028 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173029 [[ErrorPageContent alloc] initWithLoader:self
3030 browserState:self.browserState
3031 url:url
3032 error:error
3033 isPost:isPost
3034 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133035 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3036 return errorPageContent;
3037}
3038
3039- (BOOL)hasControllerForURL:(const GURL&)url {
3040 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453041 if (host == kChromeUIOfflineHost) {
3042 // Only allow offline URL that are fully specified.
3043 return reading_list::IsOfflineURLValid(
3044 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3045 }
sdefresnee65fd872016-12-19 13:38:133046
michaeldo352029b2017-05-10 20:41:383047 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost;
sdefresnee65fd872016-12-19 13:38:133048}
3049
olivierrobind43eecb2017-01-27 20:35:263050- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3051 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133052 DCHECK(url.SchemeIs(kChromeUIScheme));
3053
3054 id<CRWNativeContent> nativeController = nil;
3055 std::string url_host = url.host();
3056 if (url_host == kChromeUINewTabHost || url_host == kChromeUIBookmarksHost) {
3057 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173058 [[NewTabPageController alloc] initWithUrl:url
3059 loader:self
3060 focuser:_toolbarController
3061 ntpObserver:self
3062 browserState:_browserState
3063 colorCache:_dominantColorCache
3064 webToolbarDelegate:self
justincohenbc913632017-04-18 14:41:453065 tabModel:_model
justincohen75011c32017-04-28 16:31:393066 parentViewController:self
3067 dispatcher:_dispatcher];
sdefresnee65fd872016-12-19 13:38:133068 pageController.swipeRecognizerProvider = self.sideSwipeController;
3069
3070 // Panel is always NTP for iPhone.
gambard79b688f2017-07-06 09:00:253071 NewTabPage::PanelIdentifier panelType = NewTabPage::kHomePanel;
sdefresnee65fd872016-12-19 13:38:133072
3073 if (IsIPadIdiom()) {
3074 // New Tab Page can have multiple panels. Each panel is addressable
3075 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3076 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3077 // the Bookmark Manager, etc.
3078 // The utility functions NewTabPage::IdentifierFromFragment() and
3079 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3080 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3081 // without changing the URL since the URL may be chrome://bookmarks/#123.
3082 // If the URL is chrome://newtab/, pre-select the panel based on the
3083 // #fragment.
3084 panelType = url_host == kChromeUIBookmarksHost
3085 ? NewTabPage::kBookmarksPanel
3086 : NewTabPage::IdentifierFromFragment(url.ref());
3087 }
3088 [pageController selectPanel:panelType];
3089 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453090 } else if (url_host == kChromeUIOfflineHost &&
3091 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133092 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173093 [[OfflinePageNativeContent alloc] initWithLoader:self
3094 browserState:_browserState
3095 webState:webState
3096 URL:url];
sdefresnee65fd872016-12-19 13:38:133097 [self setOverScrollActionControllerToStaticNativeContent:
3098 staticNativeController];
3099 nativeController = staticNativeController;
3100 } else if (url_host == kChromeUIExternalFileHost) {
3101 // Return an instance of the |ExternalFileController| only if the file is
3102 // still in the sandbox.
3103 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3104 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173105 nativeController =
3106 [[ExternalFileController alloc] initWithURL:url
3107 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133108 }
peterlaurens44615d02017-05-23 20:23:093109 } else if (url_host == kChromeUICrashHost) {
3110 // There is no native controller for kChromeUICrashHost, it is instead
3111 // handled as any other renderer crash by the SadTabTabHelper.
3112 // nativeController must be set to nil to prevent defaulting to a
3113 // PageNotAvailableController.
3114 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133115 } else {
3116 DCHECK(![self hasControllerForURL:url]);
3117 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173118 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133119 }
3120 // If a native controller is vended before its tab is added to the tab model,
3121 // use the temporary key and add it under the new tab's tabId in the
3122 // TabModelObserver callback. This happens:
3123 // - when there is no current tab (occurs when vending the NTP controller for
3124 // the first tab that is opened),
3125 // - when the current tab's url doesn't match |url| (occurs when a native
3126 // controller is opened in a new tab)
3127 // - when the current tab's url matches |url| and there is already a native
3128 // controller of the appropriate type vended to it (occurs when a native
3129 // controller is opened in a new tab from a tab with a matching URL, e.g.
3130 // opening an NTP when an NTP is already displayed in the current tab).
3131 // For normal page loads, history navigations, tab restorations, and crash
3132 // recoveries, the tab will already exist in the tab model and the tabId can
3133 // be used as the native controller key.
3134 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3135 // that native controllers vended here always correspond to the current tab.
3136 Tab* currentTab = [_model currentTab];
3137 NSString* nativeControllerKey = currentTab.tabId;
kkhorimotob110b262017-06-01 18:38:253138 if (!currentTab || currentTab.lastCommittedURL != url ||
sdefresnee65fd872016-12-19 13:38:133139 [[_nativeControllersForTabIDs objectForKey:nativeControllerKey]
3140 isKindOfClass:[nativeController class]]) {
3141 nativeControllerKey = kNativeControllerTemporaryKey;
3142 }
3143 DCHECK(nativeControllerKey);
3144 [_nativeControllersForTabIDs setObject:nativeController
3145 forKey:nativeControllerKey];
3146 return nativeController;
3147}
3148
3149- (id)nativeControllerForTab:(Tab*)tab {
3150 id nativeController = [_nativeControllersForTabIDs objectForKey:tab.tabId];
3151 if (!nativeController) {
3152 // If there is no controller, check for a native controller stored under
3153 // the temporary key.
3154 nativeController = [_nativeControllersForTabIDs
3155 objectForKey:kNativeControllerTemporaryKey];
3156 }
3157 return nativeController;
3158}
3159
3160#pragma mark - DialogPresenterDelegate methods
3161
3162- (void)dialogPresenter:(DialogPresenter*)presenter
3163 willShowDialogForWebState:(web::WebState*)webState {
3164 for (Tab* iteratedTab in self.tabModel) {
3165 if ([iteratedTab webState] == webState) {
3166 self.tabModel.currentTab = iteratedTab;
3167 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3168 break;
3169 }
3170 }
3171}
3172
3173#pragma mark - Context menu methods
3174
3175- (void)searchByImageAtURL:(const GURL&)url
3176 referrer:(const web::Referrer)referrer {
3177 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273178 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113179 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173180 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3181 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113182 DCHECK(data);
3183 dispatch_async(dispatch_get_main_queue(), ^{
3184 [weakSelf searchByImageData:data atURL:image_source_url];
3185 });
3186 };
3187 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133188 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3189 web::PolicyForNavigation(url, referrer));
3190}
3191
3192- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3193 NSData* imageData = data;
3194 UIImage* image = [UIImage imageWithData:imageData];
3195 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3196 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3197 // kSearchByImageMaxImageHeight).
3198 if (image &&
3199 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3200 (image.size.width > kSearchByImageMaxImageWidth ||
3201 image.size.height > kSearchByImageMaxImageHeight)) {
3202 CGSize newImageSize =
3203 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3204 image = [image gtm_imageByResizingToSize:newImageSize
3205 preserveAspectRatio:YES
3206 trimToFit:NO];
3207 imageData = UIImageJPEGRepresentation(image, 1.0);
3208 }
3209
3210 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3211 std::string byteString(bytes, [imageData length]);
3212
3213 TemplateURLService* templateUrlService =
3214 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103215 const TemplateURL* defaultURL =
3216 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133217 DCHECK(!defaultURL->image_url().empty());
3218 DCHECK(defaultURL->image_url_ref().IsValid(
3219 templateUrlService->search_terms_data()));
3220 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3221 search_args.image_url = imageURL;
3222 search_args.image_thumbnail_content = byteString;
3223
3224 // Generate the URL and populate |post_content| with the content type and
3225 // HTTP body for the request.
3226 TemplateURLRef::PostContent post_content;
3227 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3228 search_args, templateUrlService->search_terms_data(), &post_content));
3229 [self addSelectedTabWithURL:result
3230 postData:&post_content
3231 transition:ui::PAGE_TRANSITION_TYPED];
3232}
3233
3234- (void)saveImageAtURL:(const GURL&)url
3235 referrer:(const web::Referrer&)referrer {
3236 DCHECK(url.is_valid());
3237
gambard9efce7a2017-02-09 18:53:173238 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3239 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113240 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133241
gambardbbf85c42017-06-29 11:15:343242 if ([data length] == 0) {
3243 [self displayPrivacyErrorAlertOnMainQueue:
3244 l10n_util::GetNSString(
3245 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3246 return;
3247 }
3248
gambard9efce7a2017-02-09 18:53:173249 base::FilePath::StringType extension;
3250
3251 bool extensionSuccess =
3252 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3253 if (!extensionSuccess || extension.length() == 0) {
3254 extension = "png";
3255 }
3256
3257 NSString* fileExtension =
3258 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3259 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113260 };
3261 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133262 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3263 web::PolicyForNavigation(url, referrer));
3264}
3265
gambard9efce7a2017-02-09 18:53:173266- (void)managePermissionAndSaveImage:(NSData*)data
3267 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133268 switch ([PHPhotoLibrary authorizationStatus]) {
3269 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173270 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133271 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3272 // Call -saveImage again to check if chrome needs to display an error or
3273 // saves the image.
3274 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173275 [self managePermissionAndSaveImage:data
3276 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133277 }];
3278 break;
stkhapuginf58b10d02017-04-10 13:36:173279 }
sdefresnee65fd872016-12-19 13:38:133280
3281 // The application doesn't have permission to access photo and the user
3282 // cannot grant it.
3283 case PHAuthorizationStatusRestricted:
3284 [self displayPrivacyErrorAlertOnMainQueue:
3285 l10n_util::GetNSString(
3286 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3287 break;
3288
3289 // The application doesn't have permission to access photo and the user
3290 // can grant it.
3291 case PHAuthorizationStatusDenied:
3292 [self displayImageErrorAlertWithSettingsOnMainQueue];
3293 break;
3294
3295 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243296 default:
3297 __weak BrowserViewController* weakSelf = self;
3298 [self saveImage:data
3299 withFileExtension:fileExtension
3300 completion:^(BOOL success, NSError* error) {
3301 [weakSelf finishSavingImageWithError:error];
3302 }];
sdefresnee65fd872016-12-19 13:38:133303 break;
sdefresnee65fd872016-12-19 13:38:133304 }
3305}
3306
Sylvain Defresnefd3ecf22017-07-12 18:47:243307- (void)saveImage:(NSData*)data
3308 withFileExtension:(NSString*)fileExtension
3309 completion:(void (^)(BOOL, NSError*))completion {
3310 base::PostTaskWithTraits(
3311 FROM_HERE,
3312 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3313 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3314 base::BindBlockArc(^{
3315 base::ThreadRestrictions::AssertIOAllowed();
sdefresnee65fd872016-12-19 13:38:133316
Sylvain Defresnefd3ecf22017-07-12 18:47:243317 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3318 stringByAppendingString:fileExtension];
3319 NSURL* fileURL = [NSURL
3320 fileURLWithPath:[NSTemporaryDirectory()
3321 stringByAppendingPathComponent:fileName]];
3322 NSError* error = nil;
3323 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3324 if (error) {
3325 if (completion)
3326 completion(NO, error);
3327 return;
3328 }
sdefresnee65fd872016-12-19 13:38:133329
Sylvain Defresnefd3ecf22017-07-12 18:47:243330 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3331 [PHAssetChangeRequest
3332 creationRequestForAssetFromImageAtFileURL:fileURL];
3333 }
3334 completionHandler:^(BOOL success, NSError* error) {
3335 base::PostTaskWithTraits(
3336 FROM_HERE,
3337 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3338 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3339 base::BindBlockArc(^{
3340 base::ThreadRestrictions::AssertIOAllowed();
3341 if (completion)
3342 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133343
Sylvain Defresnefd3ecf22017-07-12 18:47:243344 // Cleanup the temporary file.
3345 NSError* deleteFileError = nil;
3346 [[NSFileManager defaultManager]
3347 removeItemAtURL:fileURL
3348 error:&deleteFileError];
3349 }));
3350 }];
3351 }));
sdefresnee65fd872016-12-19 13:38:133352}
3353
3354- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3355 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3356 BOOL canGoToSetting =
3357 [[UIApplication sharedApplication] canOpenURL:settingURL];
3358 if (canGoToSetting) {
3359 dispatch_async(dispatch_get_main_queue(), ^{
3360 [self displayImageErrorAlertWithSettings:settingURL];
3361 });
3362 } else {
3363 [self displayPrivacyErrorAlertOnMainQueue:
3364 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3365 }
3366}
3367
3368- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3369 // Dismiss current alert.
3370 [_alertCoordinator stop];
3371
3372 NSString* title =
3373 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3374 NSString* message = l10n_util::GetNSString(
3375 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3376
stkhapuginc9eee7b2017-04-10 15:49:273377 _alertCoordinator =
3378 [[AlertCoordinator alloc] initWithBaseViewController:self
3379 title:title
3380 message:message];
sdefresnee65fd872016-12-19 13:38:133381
3382 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3383 action:nil
3384 style:UIAlertActionStyleCancel];
3385
3386 [_alertCoordinator
3387 addItemWithTitle:l10n_util::GetNSString(
3388 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3389 action:^{
3390 OpenUrlWithCompletionHandler(settingURL, nil);
3391 }
3392 style:UIAlertActionStyleDefault];
3393
3394 [_alertCoordinator start];
3395}
3396
3397- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3398 dispatch_async(dispatch_get_main_queue(), ^{
3399 NSString* title =
3400 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3401 [self showErrorAlertWithStringTitle:title message:errorContent];
3402 });
3403}
3404
3405// This callback is triggered when the image is effectively saved onto the photo
3406// album, or if the save failed for some reason.
3407- (void)finishSavingImageWithError:(NSError*)error {
3408 // Was there an error?
3409 if (error) {
3410 // Saving photo failed even though user has granted access to Photos.
3411 // Display the error information from the NSError object for user.
3412 NSString* errorMessage = [NSString
3413 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3414 [error domain], [error code]];
3415 // This code may be execute outside of the main thread. Make sure to display
3416 // the error on the main thread.
3417 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3418 } else {
3419 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3420 // the photo application. The current behaviour is to create the photo there
3421 // but not providing any link to it is suboptimal. That's what Safari is
3422 // doing, and what the PM want, but it doesn't make it right.
3423 }
3424}
3425
sdefresnee65fd872016-12-19 13:38:133426#pragma mark - Showing popups
3427
sdefresnee65fd872016-12-19 13:38:133428- (void)showPageInfoPopupForView:(UIView*)sourceView {
3429 Tab* tab = [_model currentTab];
3430 DCHECK([tab navigationManager]);
3431 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3432
3433 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3434 // be trigerred by a button enabled when a current item matches some
3435 // conditions. However a crash was seen were navItem was NULL hence this
3436 // test after a DCHECK.
3437 DCHECK(navItem);
3438 if (!navItem)
3439 return;
3440
olivierrobin013ba672017-03-01 21:16:243441 // Don't show if the page is native except for offline pages (to show the
3442 // offline page info).
3443 if ([self isTabNativePage:tab] &&
3444 !reading_list::IsOfflineURL(navItem->GetURL())) {
sdefresnee65fd872016-12-19 13:38:133445 return;
olivierrobin013ba672017-03-01 21:16:243446 }
sdefresnee65fd872016-12-19 13:38:133447
justincohenb1a73cf2017-02-06 20:25:433448 // Don't show the bubble twice (this can happen when tapping very quickly in
3449 // accessibility mode).
3450 if (_pageInfoController)
3451 return;
3452
sdefresnee65fd872016-12-19 13:38:133453 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3454
3455 // Dismiss the omnibox (if open).
3456 [_toolbarController cancelOmniboxEdit];
3457
3458 [[NSNotificationCenter defaultCenter]
3459 postNotificationName:ios_internal::kPageInfoWillShowNotification
3460 object:nil];
3461
3462 // TODO(rohitrao): Get rid of PageInfoModel completely.
3463 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3464 PageInfoModel* pageInfoModel = new PageInfoModel(
3465 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3466
3467 UIView* view = [self view];
stkhapuginc9eee7b2017-04-10 15:49:273468 _pageInfoController = [[PageInfoViewController alloc]
sdefresnee65fd872016-12-19 13:38:133469 initWithModel:pageInfoModel
3470 bridge:bridge
3471 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
stkhapuginc9eee7b2017-04-10 15:49:273472 parentView:view];
Mark Coganb9aac6432017-07-07 13:26:353473 _pageInfoController.dispatcher = self.dispatcher;
stkhapuginc9eee7b2017-04-10 15:49:273474 bridge->set_controller(_pageInfoController);
sdefresnee65fd872016-12-19 13:38:133475}
3476
3477- (void)hidePageInfoPopupForView:(UIView*)sourceView {
3478 [_pageInfoController dismiss];
stkhapuginc9eee7b2017-04-10 15:49:273479 _pageInfoController = nil;
sdefresnee65fd872016-12-19 13:38:133480}
3481
3482- (void)showSecurityHelpPage {
3483 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3484 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133485 inBackground:NO
3486 appendTo:kCurrentTab];
3487 [self hidePageInfoPopupForView:nil];
3488}
3489
3490- (void)showTabHistoryPopupForBackwardHistory {
3491 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133492
3493 // Dismiss the omnibox (if open).
3494 [_toolbarController cancelOmniboxEdit];
3495 // Dismiss the soft keyboard (if open).
3496 Tab* tab = [_model currentTab];
3497 [tab.webController dismissKeyboard];
kkhorimoto1c12cbf2017-03-14 02:57:133498 web::NavigationItemList backwardItems =
3499 [tab navigationManager]->GetBackwardItems();
sdefresnee65fd872016-12-19 13:38:133500 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133501 withItems:backwardItems
sdefresnee65fd872016-12-19 13:38:133502 forBackHistory:YES];
3503}
3504
3505- (void)showTabHistoryPopupForForwardHistory {
3506 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133507
3508 // Dismiss the omnibox (if open).
3509 [_toolbarController cancelOmniboxEdit];
3510 // Dismiss the soft keyboard (if open).
3511 Tab* tab = [_model currentTab];
3512 [tab.webController dismissKeyboard];
3513
kkhorimoto1c12cbf2017-03-14 02:57:133514 web::NavigationItemList forwardItems =
3515 [tab navigationManager]->GetForwardItems();
sdefresnee65fd872016-12-19 13:38:133516 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133517 withItems:forwardItems
sdefresnee65fd872016-12-19 13:38:133518 forBackHistory:NO];
3519}
3520
3521- (void)navigateToSelectedEntry:(id)sender {
3522 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
3523 TabHistoryCell* selectedCell = (TabHistoryCell*)sender;
kkhorimoto7aed9e262017-03-04 02:28:553524 [[_model currentTab] goToItem:selectedCell.item];
sdefresnee65fd872016-12-19 13:38:133525 [_toolbarController dismissTabHistoryPopup];
3526}
3527
sdefresnee65fd872016-12-19 13:38:133528- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133529 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3530
3531 ReadingListModel* readingModel =
3532 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013533 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3534 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133535
pinkerton07e27842017-03-02 15:29:023536 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033537 [self showSnackbar:l10n_util::GetNSString(
3538 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133539}
3540
3541#pragma mark - Keyboard commands management
3542
3543- (BOOL)shouldRegisterKeyboardCommands {
3544 if ([self presentedViewController])
3545 return NO;
3546
3547 if (_voiceSearchController && _voiceSearchController->IsVisible())
3548 return NO;
3549
3550 // If there is no first responder, try to make the webview the first
3551 // responder.
3552 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273553 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133554 }
3555
3556 return YES;
3557}
3558
3559- (KeyCommandsProvider*)keyCommandsProvider {
3560 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273561 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133562 }
stkhapuginc9eee7b2017-04-10 15:49:273563 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133564}
3565
3566#pragma mark - KeyCommandsPlumbing
3567
3568- (BOOL)isOffTheRecord {
3569 return _isOffTheRecord;
3570}
3571
3572- (NSUInteger)tabsCount {
3573 return [_model count];
3574}
3575
lpromero47ea8862017-01-13 17:51:063576- (BOOL)canGoBack {
3577 return [_model currentTab].canGoBack;
3578}
3579
3580- (BOOL)canGoForward {
3581 return [_model currentTab].canGoForward;
3582}
3583
sdefresnee65fd872016-12-19 13:38:133584- (void)focusTabAtIndex:(NSUInteger)index {
3585 if ([_model count] > index) {
3586 [_model setCurrentTab:[_model tabAtIndex:index]];
3587 }
3588}
3589
3590- (void)focusNextTab {
3591 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3592 NSInteger modelCount = [_model count];
3593 if (currentTabIndex < modelCount - 1) {
3594 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3595 [_model setCurrentTab:nextTab];
3596 } else {
3597 [_model setCurrentTab:[_model tabAtIndex:0]];
3598 }
3599}
3600
3601- (void)focusPreviousTab {
3602 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3603 if (currentTabIndex > 0) {
3604 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3605 [_model setCurrentTab:previousTab];
3606 } else {
3607 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3608 [_model setCurrentTab:lastTab];
3609 }
3610}
3611
3612- (void)reopenClosedTab {
3613 sessions::TabRestoreService* const tabRestoreService =
3614 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3615 if (!tabRestoreService || tabRestoreService->entries().empty())
3616 return;
3617
3618 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3619 tabRestoreService->entries().front();
3620 // Only handle the TAB type.
3621 if (entry->type != sessions::TabRestoreService::TAB)
3622 return;
3623
Mark Cogandfcdea72017-07-18 13:47:383624 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133625 TabRestoreServiceDelegateImplIOS* const delegate =
3626 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3627 _browserState);
3628 tabRestoreService->RestoreEntryById(delegate, entry->id,
3629 WindowOpenDisposition::CURRENT_TAB);
3630}
3631
3632- (void)focusOmnibox {
3633 [_toolbarController focusOmnibox];
3634}
3635
3636#pragma mark - UIResponder
3637
3638- (NSArray*)keyCommands {
3639 if (![self shouldRegisterKeyboardCommands]) {
3640 return nil;
3641 }
3642 return [self.keyCommandsProvider
3643 keyCommandsForConsumer:self
Mark Cogan6c58ea92017-07-06 13:08:243644 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133645 editingText:![self isFirstResponder]];
3646}
3647
3648#pragma mark -
3649
3650// Induce an intentional crash in the browser process.
3651- (void)induceBrowserCrash {
3652 CHECK(false);
3653 // Call another function, so that the above CHECK can't be tail-call
3654 // optimized. This ensures that this method's name will show up in the stack
3655 // for easier identification.
3656 CHECK(true);
3657}
3658
3659- (void)loadURL:(const GURL&)url
3660 referrer:(const web::Referrer&)referrer
3661 transition:(ui::PageTransition)transition
3662 rendererInitiated:(BOOL)rendererInitiated {
3663 [[OmniboxGeolocationController sharedInstance]
3664 locationBarDidSubmitURL:url
3665 transition:transition
3666 browserState:_browserState];
3667
3668 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3669 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3670 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3671 }
3672
3673 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243674 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133675 // restart.
3676 if (url.host() == kChromeUIBrowserCrashHost) {
3677 [self induceBrowserCrash];
3678 // In debug the app can continue working even after the CHECK. Adding a
3679 // return avoids the crash url to be added to the history.
3680 return;
3681 }
3682
3683 if (url == [_preloadController prerenderedURL]) {
sdefresne2c600c52017-04-04 16:49:593684 std::unique_ptr<web::WebState> newWebState =
3685 [_preloadController releasePrerenderContents];
3686 DCHECK(newWebState);
3687
sdefresnee65fd872016-12-19 13:38:133688 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593689 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133690 DCHECK(oldTab);
3691 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593692
kkhorimotod804c5732017-03-15 23:44:523693 bool canPruneItems =
3694 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593695
kkhorimotod804c5732017-03-15 23:44:523696 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523697 [newTab navigationManager]->CopyStateFromAndPrune(
3698 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593699
3700 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3701 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133702
3703 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3704 // BrowserViewController to detect that a pre-rendered tab is switched in,
3705 // and show the prerendering animation.
3706 newTab.isPrerenderTab = NO;
3707
sdefresne2f7781c2017-03-02 19:12:463708 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133709 return;
3710 }
3711 }
3712
3713 GURL urlToLoad = url;
3714 if ([_preloadController hasPrefetchedURL:url]) {
3715 // Prefetched URLs have modified URLs, so load the prefetched version of
3716 // |url| instead of the original |url|.
3717 urlToLoad = [_preloadController prefetchedURL];
3718 }
3719
3720 [_preloadController cancelPrerender];
3721
3722 // Some URLs are not allowed while in incognito. If we are in incognito and
3723 // load a disallowed URL, instead create a new tab not in the incognito state.
3724 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3725 [self webPageOrderedOpen:url
3726 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133727 inIncognito:NO
3728 inBackground:NO
3729 appendTo:kCurrentTab];
3730 return;
3731 }
3732
mrefaata84d5a02017-06-08 17:13:293733 // If this is a reload initiated from the omnibox.
3734 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3735 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3736 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3737 [[_model currentTab] navigationManager]->Reload(
3738 web::ReloadType::NORMAL, true /* check_for_repost */);
3739 return;
3740 }
3741
sdefresnee65fd872016-12-19 13:38:133742 web::NavigationManager::WebLoadParams params(urlToLoad);
3743 params.referrer = referrer;
3744 params.transition_type = transition;
3745 params.is_renderer_initiated = rendererInitiated;
3746 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233747 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133748}
3749
3750- (void)loadJavaScriptFromLocationBar:(NSString*)script {
3751 [_preloadController cancelPrerender];
3752 DCHECK([_model currentTab]);
3753 [[_model currentTab].webController executeUserJavaScript:script
3754 completionHandler:nil];
3755}
3756
3757- (web::WebState*)currentWebState {
3758 return [[_model currentTab] webState];
3759}
3760
3761// This is called from within an animation block.
3762- (void)toolbarHeightChanged {
3763 if ([self headerHeight] != 0) {
3764 // Ensure full screen height is updated.
3765 Tab* currentTab = [_model currentTab];
3766 BOOL visible = self.isToolbarOnScreen;
3767 [currentTab updateFullscreenWithToolbarVisible:visible];
3768 }
3769}
3770
3771// Load a new URL on a new page/tab.
3772- (void)webPageOrderedOpen:(const GURL&)URL
3773 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133774 inBackground:(BOOL)inBackground
3775 appendTo:(OpenPosition)appendTo {
3776 Tab* adjacentTab = nil;
3777 if (appendTo == kCurrentTab)
3778 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353779 [_model insertTabWithURL:URL
3780 referrer:referrer
3781 transition:ui::PAGE_TRANSITION_LINK
3782 opener:adjacentTab
3783 openedByDOM:NO
3784 atIndex:TabModelConstants::kTabPositionAutomatically
3785 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133786}
3787
3788- (void)webPageOrderedOpen:(const GURL&)url
3789 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133790 inIncognito:(BOOL)inIncognito
3791 inBackground:(BOOL)inBackground
3792 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003793 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:233794 // feature_engagement::Tracker based on |inIncognito|.
3795 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003796
sdefresnee65fd872016-12-19 13:38:133797 if (inIncognito == _isOffTheRecord) {
3798 [self webPageOrderedOpen:url
3799 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133800 inBackground:inBackground
3801 appendTo:appendTo];
3802 return;
3803 }
3804 // When sending an open command that switches modes, ensure the tab
3805 // ends up appended to the end of the model, not just next to what is
3806 // currently selected in the other mode. This is done with the |append|
3807 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273808 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133809 initWithURL:url
3810 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133811 inIncognito:inIncognito
3812 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273813 appendTo:kLastTab];
sdefresnee65fd872016-12-19 13:38:133814 [self chromeExecuteCommand:command];
3815}
3816
3817- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
3818 [[_model currentTab] loadSessionTab:sessionTab];
3819}
3820
3821- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433822 DCHECK(javascript);
3823 javascript = [javascript stringByRemovingPercentEncoding];
3824 web::WebState* webState = [[_model currentTab] webState];
3825 if (webState) {
3826 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3827 }
sdefresnee65fd872016-12-19 13:38:133828}
3829
3830#pragma mark - WebToolbarDelegate methods
3831
3832- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3833 if (_locationBarHasFocus)
3834 return; // TODO(crbug.com/244366): This should not be necessary.
3835 _locationBarHasFocus = YES;
3836 [[NSNotificationCenter defaultCenter]
3837 postNotificationName:ios_internal::
3838 kLocationBarBecomesFirstResponderNotification
3839 object:nil];
3840 [_sideSwipeController setEnabled:NO];
3841 if ([[_model currentTab].webController wantsKeyboardShield]) {
3842 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3843 [_typingShield setAlpha:0.0];
3844 [_typingShield setHidden:NO];
3845 [UIView animateWithDuration:0.3
3846 animations:^{
3847 [_typingShield setAlpha:1.0];
3848 }];
3849 }
3850 [[OmniboxGeolocationController sharedInstance]
3851 locationBarDidBecomeFirstResponder:_browserState];
3852}
3853
3854- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3855 if (!_locationBarHasFocus)
3856 return; // TODO(crbug.com/244366): This should not be necessary.
3857 _locationBarHasFocus = NO;
3858 [_sideSwipeController setEnabled:YES];
3859 [[NSNotificationCenter defaultCenter]
3860 postNotificationName:ios_internal::
3861 kLocationBarResignsFirstResponderNotification
3862 object:nil];
3863 [UIView animateWithDuration:0.3
3864 animations:^{
3865 [_typingShield setAlpha:0.0];
3866 }
3867 completion:^(BOOL finished) {
3868 // This can happen if one quickly resigns the omnibox and then taps
3869 // on the omnibox again during this animation. If the animation is
3870 // interrupted and the toolbar controller is first responder, it's safe
3871 // to assume the |_typingShield| shouldn't be hidden here.
3872 if (!finished && [_toolbarController isOmniboxFirstResponder])
3873 return;
3874 [_typingShield setHidden:YES];
3875 }];
3876 [[OmniboxGeolocationController sharedInstance]
3877 locationBarDidResignFirstResponder:_browserState];
3878
3879 // If a load was cancelled by an omnibox edit, but nothing is loading when
3880 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3881 if (_locationBarEditCancelledLoad) {
3882 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:293883
3884 web::WebState* webState = [_model currentTab].webState;
3885 if (!_toolbarModelIOS->IsLoading() && webState)
3886 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3887 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133888 }
3889}
3890
3891- (IBAction)locationBarBeganEdit:(id)sender {
3892 // On handsets, if a page is currently loading it should be stopped.
3893 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:353894 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:133895 _locationBarEditCancelledLoad = YES;
3896 }
3897}
3898
3899- (IBAction)prepareToEnterTabSwitcher:(id)sender {
3900 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
3901}
3902
3903- (ToolbarModelIOS*)toolbarModelIOS {
3904 return _toolbarModelIOS.get();
3905}
3906
3907- (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
3908 [_toolbarController setBackgroundAlpha:alpha];
3909}
3910
3911- (void)updateToolbarControlsAlpha:(CGFloat)alpha {
3912 [_toolbarController setControlsAlpha:alpha];
3913}
3914
3915- (void)willUpdateToolbarSnapshot {
3916 [[_model currentTab].overscrollActionsController clear];
3917}
3918
3919- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
3920 CGRect frame = [_contentArea frame];
3921 if (!fullScreen) {
3922 // Changing the origin here is unnecessary, it's set in page_animation_util.
3923 frame.size.height -= [self headerHeight];
3924 }
3925
3926 CGFloat shortAxis = frame.size.width;
3927 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
3928 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
3929 CGFloat aspectRatio = frame.size.height / frame.size.width;
3930 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
3931 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
3932 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
3933 CGRect cardFrame = {frame.origin, cardSize};
3934
3935 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:173936 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133937 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
3938 : CardCloseButtonSide::LEADING;
3939 [_contentArea addSubview:card];
3940 return card;
3941}
3942
Mark Cogan6ebbde02017-07-07 12:50:133943#pragma mark - BrowserCommands
3944
3945- (void)goBack {
3946 [[_model currentTab] goBack];
3947}
3948
3949- (void)goForward {
3950 [[_model currentTab] goForward];
3951}
3952
Mark Coganb9aac6432017-07-07 13:26:353953- (void)stopLoading {
3954 [_model currentTab].webState->Stop();
3955}
3956
3957- (void)reload {
3958 web::WebState* webState = [_model currentTab].webState;
3959 if (webState) {
3960 // |check_for_repost| is true because the reload is explicitly initiated
3961 // by the user.
3962 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3963 true /* check_for_repost */);
3964 }
3965}
3966
Mark Cogan8e791022017-07-10 09:55:353967- (void)sharePage {
3968 ShareToData* data = activity_services::ShareToDataForTab([_model currentTab]);
3969 if (data)
3970 [self sharePageWithData:data];
3971}
3972
3973- (void)bookmarkPage {
3974 [self initializeBookmarkInteractionController];
3975 [_bookmarkInteractionController
3976 presentBookmarkForTab:[_model currentTab]
3977 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
3978 inView:[_toolbarController bookmarkButtonView]
3979 originRect:[_toolbarController bookmarkButtonAnchorRect]];
3980}
3981
Mark Cogan6acee7f2017-07-11 09:01:403982- (void)showToolsMenu {
3983 DCHECK(_browserState);
3984 DCHECK(self.visible || self.dismissingModal);
3985
3986 // Record the time this menu was requested; to be stored in the configuration
3987 // object.
3988 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
3989
3990 // Dismiss the omnibox (if open).
3991 [_toolbarController cancelOmniboxEdit];
3992 // Dismiss the soft keyboard (if open).
3993 [[_model currentTab].webController dismissKeyboard];
3994 // Dismiss Find in Page focus.
3995 [self updateFindBar:NO shouldFocus:NO];
3996
3997 ToolsMenuConfiguration* configuration =
3998 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
3999 configuration.requestStartTime =
4000 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4001 if ([_model count] == 0)
4002 [configuration setNoOpenedTabs:YES];
4003
4004 if (_isOffTheRecord)
4005 [configuration setInIncognito:YES];
4006
4007 if (!_readingListMenuNotifier) {
4008 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4009 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4010 _browserState)];
4011 }
Cooper Knaake4f495cf2017-07-27 23:30:034012
4013 feature_engagement::Tracker* engagementTracker =
4014 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4015 if (engagementTracker->ShouldTriggerHelpUI(
4016 feature_engagement::kIPHBadgedReadingListFeature)) {
4017 [configuration setShowReadingListNewBadge:YES];
4018 [configuration setEngagementTracker:engagementTracker];
4019 }
Mark Cogan6acee7f2017-07-11 09:01:404020 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4021
4022 [configuration setUserAgentType:self.userAgentType];
4023
4024 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
4025
4026 ToolsPopupController* toolsPopupController =
4027 [_toolbarController toolsPopupController];
4028 if ([_model currentTab]) {
4029 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4030 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4031 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
4032 [toolsPopupController setCanUseReaderMode:self.canUseReaderMode];
4033 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4034
4035 if (!IsIPadIdiom())
4036 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4037 }
4038}
4039
Mark Cogandfcdea72017-07-18 13:47:384040- (void)openNewTab:(OpenNewTabCommand*)command {
4041 if (self.isOffTheRecord != command.incognito) {
4042 // Not for this browser state, send it on its way.
4043 [self.dispatcher switchModesAndOpenNewTab:command];
4044 return;
4045 }
4046
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004047 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234048 // events to the feature_engagement::Tracker based on |command.userInitiated|
4049 // and |command.incognito|.
4050 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004051
Mark Cogandfcdea72017-07-18 13:47:384052 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4053 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494054 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4055 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384056 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494057 if (oldForegroundTabWasAddedCompletionBlock) {
4058 oldForegroundTabWasAddedCompletionBlock();
4059 }
Mark Cogandfcdea72017-07-18 13:47:384060 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4061 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4062 if (offTheRecord) {
4063 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4064 timeDelta);
4065 } else {
4066 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4067 }
4068 };
4069
4070 [self setLastTapPoint:command];
4071 DCHECK(self.visible || self.dismissingModal);
4072 Tab* currentTab = [_model currentTab];
4073 if (currentTab) {
4074 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4075 }
4076 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4077 transition:ui::PAGE_TRANSITION_TYPED];
4078}
4079
Mark Cogan123895002017-07-20 12:54:064080- (void)printTab {
4081 Tab* currentTab = [_model currentTab];
4082 // The UI should prevent users from printing non-printable pages. However, a
4083 // redirection to an un-printable page can happen before it is reflected in
4084 // the UI.
4085 if (![currentTab viewForPrinting]) {
4086 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
4087 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
4088 return;
4089 }
4090 DCHECK(_browserState);
4091 if (!_printController) {
4092 _printController = [[PrintController alloc]
4093 initWithContextGetter:_browserState->GetRequestContext()];
4094 }
4095 [_printController printView:[currentTab viewForPrinting]
4096 withTitle:[currentTab title]
4097 viewController:self];
4098}
4099
Mark Coganfa25b052017-07-20 17:31:034100- (void)addToReadingList:(ReadingListAddCommand*)command {
4101 [self addToReadingListURL:[command URL] title:[command title]];
4102}
4103
Jean-François Geyelin709777e2017-07-27 09:10:464104- (void)showQRScanner {
4105 _qrScannerViewController =
4106 [[QRScannerViewController alloc] initWithDelegate:_toolbarController];
4107 [self presentViewController:[_qrScannerViewController
4108 getViewControllerToPresent]
4109 animated:YES
4110 completion:nil];
4111}
4112
sdefresnee65fd872016-12-19 13:38:134113#pragma mark - Command Handling
4114
4115- (IBAction)chromeExecuteCommand:(id)sender {
4116 NSInteger command = [sender tag];
4117
4118 if (!_model || !_browserState)
4119 return;
rohitrao005a6432017-03-16 20:52:424120 Tab* currentTab = [_model currentTab];
sdefresnee65fd872016-12-19 13:38:134121
4122 switch (command) {
sdefresnee65fd872016-12-19 13:38:134123 case IDC_FIND:
4124 [self initFindBarForTab];
4125 break;
rohitraob2bf3cb2017-02-10 14:10:364126 case IDC_FIND_NEXT: {
rohitrao005a6432017-03-16 20:52:424127 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134128 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424129 FindTabHelper::FromWebState(currentTab.webState)
4130 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4131 [_findBarController updateResultsCount:model];
4132 });
sdefresnee65fd872016-12-19 13:38:134133 break;
rohitraob2bf3cb2017-02-10 14:10:364134 }
4135 case IDC_FIND_PREVIOUS: {
rohitrao005a6432017-03-16 20:52:424136 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134137 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424138 FindTabHelper::FromWebState(currentTab.webState)
4139 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4140 [_findBarController updateResultsCount:model];
4141 });
sdefresnee65fd872016-12-19 13:38:134142 break;
rohitraob2bf3cb2017-02-10 14:10:364143 }
sdefresnee65fd872016-12-19 13:38:134144 case IDC_FIND_CLOSE:
4145 [self closeFindInPage];
4146 break;
4147 case IDC_FIND_UPDATE:
4148 [self searchFindInPage];
4149 break;
sdefresnee65fd872016-12-19 13:38:134150 case IDC_HELP_PAGE_VIA_MENU:
4151 [self showHelpPage];
4152 break;
sdefresnee65fd872016-12-19 13:38:134153 case IDC_PRELOAD_VOICE_SEARCH:
4154 // Preload VoiceSearchController and views and view controllers needed
4155 // for voice search.
4156 [self ensureVoiceSearchControllerCreated];
4157 _voiceSearchController->PrepareToAppear();
4158 break;
sdefresnee65fd872016-12-19 13:38:134159 case IDC_SHOW_MAIL_COMPOSER:
4160 [self showMailComposer:sender];
4161 break;
4162 case IDC_READER_MODE:
4163 [[_model currentTab] switchToReaderMode];
4164 break;
4165 case IDC_REQUEST_DESKTOP_SITE:
liaoyuke6ab362012017-04-12 16:10:074166 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
sdefresnee65fd872016-12-19 13:38:134167 break;
liaoyukeea9f3ee62017-03-07 22:05:394168 case IDC_REQUEST_MOBILE_SITE:
Yuke Liao705611d2017-06-01 18:03:064169 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
liaoyukeea9f3ee62017-03-07 22:05:394170 break;
sdefresnee65fd872016-12-19 13:38:134171 case IDC_SHOW_BOOKMARK_MANAGER: {
4172 if (IsIPadIdiom()) {
4173 [self showAllBookmarks];
4174 } else {
4175 [self initializeBookmarkInteractionController];
4176 [_bookmarkInteractionController presentBookmarks];
4177 }
4178 break;
4179 }
4180 case IDC_SHOW_OTHER_DEVICES: {
4181 if (IsIPadIdiom()) {
4182 [self showNTPPanel:NewTabPage::kOpenTabsPanel];
4183 } else {
4184 UIViewController* controller = [RecentTabsPanelViewController
4185 controllerToPresentForBrowserState:_browserState
4186 loader:self];
4187 controller.modalPresentationStyle = UIModalPresentationFormSheet;
4188 controller.modalPresentationCapturesStatusBarAppearance = YES;
4189 [self presentViewController:controller animated:YES completion:nil];
4190 }
4191 break;
4192 }
sdefresnee65fd872016-12-19 13:38:134193#if !defined(NDEBUG)
4194 case IDC_VIEW_SOURCE:
4195 [self viewSource];
4196 break;
4197#endif
4198 case IDC_SHOW_PAGE_INFO:
4199 DCHECK([sender isKindOfClass:[UIButton class]]);
4200 [self showPageInfoPopupForView:sender];
4201 break;
4202 case IDC_HIDE_PAGE_INFO:
4203 [[NSNotificationCenter defaultCenter]
4204 postNotificationName:ios_internal::kPageInfoWillHideNotification
4205 object:nil];
4206 [self hidePageInfoPopupForView:sender];
4207 break;
4208 case IDC_SHOW_SECURITY_HELP:
4209 [self showSecurityHelpPage];
4210 break;
4211 case IDC_SHOW_BACK_HISTORY:
4212 [self showTabHistoryPopupForBackwardHistory];
4213 break;
4214 case IDC_SHOW_FORWARD_HISTORY:
4215 [self showTabHistoryPopupForForwardHistory];
4216 break;
4217 case IDC_BACK_FORWARD_IN_TAB_HISTORY:
4218 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
4219 [self navigateToSelectedEntry:sender];
4220 break;
sdefresnee65fd872016-12-19 13:38:134221 case IDC_RATE_THIS_APP:
4222 [self showRateThisAppDialog];
4223 break;
4224 case IDC_SHOW_READING_LIST:
sdefresnee65fd872016-12-19 13:38:134225 [self showReadingList];
4226 break;
Jean-François Geyelin5d2e184c2017-07-28 19:48:004227 case IDC_VOICE_SEARCH: {
sdefresnee65fd872016-12-19 13:38:134228 // If the voice search command is coming from a UIView sender, store it
4229 // before sending the command up the responder chain.
Jean-François Geyelin5d2e184c2017-07-28 19:48:004230 StartVoiceSearchCommand* command = [[StartVoiceSearchCommand alloc]
4231 initWithOriginView:base::mac::ObjCCast<UIView>(sender)];
4232 [self.dispatcher startVoiceSearch:command];
4233 } break;
sdefresnee65fd872016-12-19 13:38:134234 default:
4235 // Unknown commands get sent up the responder chain.
4236 [super chromeExecuteCommand:sender];
4237 break;
4238 }
4239}
4240
4241- (void)closeCurrentTab {
4242 Tab* currentTab = [_model currentTab];
4243 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4244 if (tabIndex == NSNotFound)
4245 return;
4246
jif7fed8122017-02-08 13:15:254247 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4248 // iPad.
sdefresnee65fd872016-12-19 13:38:134249 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4250 exitingPage.image =
4251 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4252
4253 // Close the actual tab, and add its image as a subview.
4254 [_model closeTabAtIndex:tabIndex];
4255
4256 // Do not animate close in iPad.
4257 if (!IsIPadIdiom()) {
4258 [_contentArea addSubview:exitingPage];
4259 ios_internal::page_animation_util::AnimateOutWithCompletion(
4260 exitingPage, 0, YES, IsPortrait(), ^{
4261 [exitingPage removeFromSuperview];
4262 });
4263 }
4264}
4265
sdefresnee65fd872016-12-19 13:38:134266
4267- (void)sharePageWithData:(ShareToData*)data {
4268 id<ShareProtocol> controller = [_dependencyFactory shareControllerInstance];
4269 if ([controller isActive])
4270 return;
4271 CGRect fromRect = [_toolbarController shareButtonAnchorRect];
4272 UIView* inView = [_toolbarController shareButtonView];
4273 [controller shareWithData:data
4274 controller:self
4275 browserState:_browserState
Mark Cogan123895002017-07-20 12:54:064276 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:134277 shareToDelegate:self
4278 fromRect:fromRect
4279 inView:inView];
4280}
4281
4282- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
4283 [[_dependencyFactory shareControllerInstance] cancelShareAnimated:NO];
4284 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4285 [_bookmarkInteractionController dismissSnackbar];
4286 [_toolbarController cancelOmniboxEdit];
4287 [_dialogPresenter cancelAllDialogs];
4288 [self hidePageInfoPopupForView:nil];
4289 if (_voiceSearchController)
4290 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364291
4292 Tab* currentTab = [_model currentTab];
4293 [currentTab dismissModals];
4294
rohitrao005a6432017-03-16 20:52:424295 if (currentTab) {
4296 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4297 if (findHelper) {
4298 findHelper->StopFinding(^{
4299 [self updateFindBar:NO shouldFocus:NO];
4300 });
4301 }
4302 }
rohitraob2bf3cb2017-02-10 14:10:364303
sdefresnee65fd872016-12-19 13:38:134304 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134305 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274306 _printController = nil;
jif7fed8122017-02-08 13:15:254307 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134308 [_contextMenuCoordinator stop];
4309 [self dismissRateThisAppDialog];
4310
4311 if (self.presentedViewController) {
4312 // Dismisses any other modal controllers that may be present, e.g. Recent
4313 // Tabs.
4314 // Note that currently, some controllers like the bookmark ones were already
4315 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4316 // but are still reported as the presentedViewController. The result is that
4317 // this will call -dismissViewControllerAnimated:completion: a second time
4318 // on it. It is not per se an issue, as it is a no-op. The problem is that
4319 // in such a case, the completion block is not called.
4320 // To ensure the completion is called, nil is passed here, and the
4321 // completion is called below.
4322 [self dismissViewControllerAnimated:NO completion:nil];
4323 // Dismissed controllers will be so after a delay. Queue the completion
4324 // callback after that.
4325 if (completion) {
4326 dispatch_after(
4327 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4328 dispatch_get_main_queue(), ^{
4329 completion();
4330 });
4331 }
4332 } else if (completion) {
4333 // If no view controllers are presented, we should be ok with dispatching
4334 // the completion block directly.
4335 dispatch_async(dispatch_get_main_queue(), completion);
4336 }
4337}
4338
4339- (void)showHelpPage {
4340 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4341 [self webPageOrderedOpen:helpUrl
4342 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134343 inBackground:NO
4344 appendTo:kCurrentTab];
4345}
4346
sdefresnee65fd872016-12-19 13:38:134347#pragma mark - Find Bar
4348
4349- (void)hideFindBarWithAnimation:(BOOL)animate {
4350 [_findBarController hideFindBarView:animate];
4351}
4352
4353- (void)showFindBarWithAnimation:(BOOL)animate
4354 selectText:(BOOL)selectText
4355 shouldFocus:(BOOL)shouldFocus {
4356 DCHECK(_findBarController);
4357 Tab* tab = [_model currentTab];
4358 DCHECK(tab);
4359 CRWWebController* webController = tab.webController;
4360
4361 CGRect referenceFrame = CGRectZero;
4362 if (IsIPadIdiom()) {
4363 referenceFrame = webController.visibleFrame;
4364 referenceFrame.origin.y -= kIPadFindBarOverlap;
4365 } else {
4366 referenceFrame = _contentArea.frame;
4367 }
4368
4369 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4370 [_findBarController addFindBarView:animate
4371 intoView:self.view
4372 withFrame:referenceFrame
4373 alignWithFrame:omniboxFrame
4374 selectText:selectText];
4375 [self updateFindBar:YES shouldFocus:shouldFocus];
4376}
4377
4378// Create find bar controller and pass it to the web controller.
4379- (void)initFindBarForTab {
4380 if (!self.canShowFindBar)
4381 return;
4382
4383 if (!_findBarController)
stkhapuginc9eee7b2017-04-10 15:49:274384 _findBarController =
4385 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134386
4387 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:424388 DCHECK(tab);
4389 auto* helper = FindTabHelper::FromWebState(tab.webState);
4390 DCHECK(!helper->IsFindUIActive());
4391 helper->SetFindUIActive(true);
sdefresnee65fd872016-12-19 13:38:134392 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4393}
4394
4395- (void)searchFindInPage {
rohitrao005a6432017-03-16 20:52:424396 DCHECK([_model currentTab]);
4397 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
stkhapuginc9eee7b2017-04-10 15:49:274398 __weak BrowserViewController* weakSelf = self;
4399 helper->StartFinding(
4400 [_findBarController searchTerm], ^(FindInPageModel* model) {
4401 BrowserViewController* strongSelf = weakSelf;
4402 if (!strongSelf) {
4403 return;
4404 }
4405 [strongSelf->_findBarController updateResultsCount:model];
4406 });
rohitrao005a6432017-03-16 20:52:424407
sdefresnee65fd872016-12-19 13:38:134408 if (!_isOffTheRecord)
rohitrao005a6432017-03-16 20:52:424409 helper->PersistSearchTerm();
sdefresnee65fd872016-12-19 13:38:134410}
4411
4412- (void)closeFindInPage {
stkhapuginc9eee7b2017-04-10 15:49:274413 __weak BrowserViewController* weakSelf = self;
rohitrao005a6432017-03-16 20:52:424414 Tab* currentTab = [_model currentTab];
4415 if (currentTab) {
4416 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4417 [weakSelf updateFindBar:NO shouldFocus:NO];
4418 });
4419 }
sdefresnee65fd872016-12-19 13:38:134420}
4421
4422- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324423 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4424 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4425 // in a crash.
4426 if (![_model currentTab]) {
4427 return;
4428 }
rohitrao005a6432017-03-16 20:52:424429 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4430 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134431 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424432 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134433 }
4434
4435 [self setFramesForHeaders:[self headerViews]
4436 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424437 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134438 initialUpdate:initialUpdate
4439 focusTextfield:shouldFocus];
4440 } else {
4441 [self hideFindBarWithAnimation:YES];
4442 }
4443}
4444
4445- (void)showAllBookmarks {
4446 DCHECK(self.visible || self.dismissingModal);
4447 GURL URL(kChromeUIBookmarksURL);
4448 Tab* tab = [_model currentTab];
4449 web::NavigationManager::WebLoadParams params(URL);
4450 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234451 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134452}
4453
4454- (void)showReadingList {
stkhapuginc9eee7b2017-04-10 15:49:274455 _readingListCoordinator = [[ReadingListCoordinator alloc]
gambard6299cc1d2017-02-21 13:06:034456 initWithBaseViewController:self
4457 browserState:self.browserState
stkhapuginc9eee7b2017-04-10 15:49:274458 loader:self];
gambard6299cc1d2017-02-21 13:06:034459
4460 [_readingListCoordinator start];
sdefresnee65fd872016-12-19 13:38:134461}
4462
sdefresnee65fd872016-12-19 13:38:134463- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel {
4464 DCHECK(self.visible || self.dismissingModal);
4465 GURL url(kChromeUINewTabURL);
4466 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4467 if (fragment != "") {
4468 GURL::Replacements replacement;
4469 replacement.SetRefStr(fragment);
4470 url = url.ReplaceComponents(replacement);
4471 }
4472 Tab* tab = [_model currentTab];
4473 web::NavigationManager::WebLoadParams params(url);
4474 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234475 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134476}
4477
4478- (void)showRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274479 DCHECK(!_rateThisAppDialog);
sdefresnee65fd872016-12-19 13:38:134480
4481 // Store the current timestamp whenever this dialog is shown.
4482 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4483 base::Time::Now().ToInternalValue());
4484
4485 // Some versions of iOS7 do not support linking directly to the "Ratings and
4486 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4487 // links to the main appstore page for the Chrome app.
4488 NSURL* storeURL =
4489 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4490 @"MZStore.woa/wa/"
4491 @"viewContentsUserReviews?type=Purple+Software&id="
4492 @"535886823&pt=9008&ct=rating")];
4493
4494 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4495 [self clearPresentedStateWithCompletion:nil];
4496
stkhapuginc9eee7b2017-04-10 15:49:274497 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
sdefresnee65fd872016-12-19 13:38:134498 [_rateThisAppDialog setAppStoreURL:storeURL];
4499 [_rateThisAppDialog setDelegate:self];
4500 [_rateThisAppDialog show];
4501}
4502
4503- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274504 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134505 base::RecordAction(base::UserMetricsAction(
4506 "IOSRateThisAppDialogDismissedProgramatically"));
4507 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274508 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134509 }
4510}
4511
4512#if !defined(NDEBUG)
4513- (void)viewSource {
4514 Tab* tab = [_model currentTab];
4515 DCHECK(tab);
4516 CRWWebController* webController = tab.webController;
4517 NSString* script = @"document.documentElement.outerHTML;";
stkhapuginc9eee7b2017-04-10 15:49:274518 __weak Tab* weakTab = tab;
4519 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:134520 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
stkhapuginc9eee7b2017-04-10 15:49:274521 Tab* strongTab = weakTab;
sdefresnee65fd872016-12-19 13:38:134522 if (!strongTab)
4523 return;
4524 if (![result isKindOfClass:[NSString class]])
4525 result = @"Not an HTML page";
4526 std::string base64HTML;
4527 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4528 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
kkhorimotob110b262017-06-01 18:38:254529 web::Referrer referrer([strongTab lastCommittedURL],
4530 web::ReferrerPolicyDefault);
eugenebut91cfb3a2017-02-21 16:40:314531
4532 [[weakSelf tabModel]
sdefresnea6395912017-03-01 01:14:354533 insertTabWithURL:URL
4534 referrer:referrer
4535 transition:ui::PAGE_TRANSITION_LINK
4536 opener:strongTab
4537 openedByDOM:YES
4538 atIndex:TabModelConstants::kTabPositionAutomatically
4539 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:134540 };
4541 [webController executeJavaScript:script
4542 completionHandler:completionHandlerBlock];
4543}
4544#endif // !defined(NDEBUG)
4545
Jean-François Geyelin5d2e184c2017-07-28 19:48:004546- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4547 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134548 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124549 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134550 _startVoiceSearchAfterNewTabAnimation = YES;
4551 return;
4552 }
4553
4554 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4555 // dismiss the keyboard.
4556 [self closeFindInPage];
4557 [[_model currentTab].webController dismissKeyboard];
4558
4559 // Ensure that voice search objects are created.
4560 [self ensureVoiceSearchControllerCreated];
4561 [self ensureVoiceSearchBarCreated];
4562
4563 // Present voice search.
4564 [_voiceSearchBar prepareToPresentVoiceSearch];
4565 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4566 [_toolbarController cancelOmniboxEdit];
4567}
4568
4569#pragma mark - ToolbarOwner
4570
4571- (ToolbarController*)relinquishedToolbarController {
4572 if (_isToolbarControllerRelinquished)
4573 return nil;
4574
4575 ToolbarController* relinquishedToolbarController = nil;
4576 if ([_toolbarController view].hidden) {
4577 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:254578 if (currentTab && UrlHasChromeScheme(currentTab.lastCommittedURL)) {
sdefresnee65fd872016-12-19 13:38:134579 // Use the native content controller's toolbar when the BVC's is hidden.
4580 id nativeController = [self nativeControllerForTab:currentTab];
4581 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4582 relinquishedToolbarController =
4583 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274584 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134585 }
4586 }
4587 } else {
stkhapuginc9eee7b2017-04-10 15:49:274588 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134589 }
4590 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4591 return relinquishedToolbarController;
4592}
4593
4594- (void)reparentToolbarController {
4595 if (_isToolbarControllerRelinquished) {
4596 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4597 // A native content controller's toolbar has been relinquished.
4598 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274599 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134600 } else if ([_findBarController isFindInPageShown]) {
4601 [self.view insertSubview:[_toolbarController view]
4602 belowSubview:[_findBarController view]];
4603 } else {
4604 [self.view addSubview:[_toolbarController view]];
4605 }
sdefresnee65fd872016-12-19 13:38:134606 _isToolbarControllerRelinquished = NO;
4607 }
4608}
4609
4610#pragma mark - TabModelObserver methods
4611
4612// Observer method, tab inserted.
4613- (void)tabModel:(TabModel*)model
4614 didInsertTab:(Tab*)tab
4615 atIndex:(NSUInteger)modelIndex
4616 inForeground:(BOOL)fg {
4617 DCHECK(tab);
4618 [self installDelegatesForTab:tab];
4619
4620 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194621 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134622 }
4623}
4624
4625// Observer method, active tab changed.
4626- (void)tabModel:(TabModel*)model
4627 didChangeActiveTab:(Tab*)newTab
4628 previousTab:(Tab*)previousTab
4629 atIndex:(NSUInteger)index {
4630 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4631 // Currently this observer method is always called with a non-nil |newTab|,
4632 // but that may change in the future. Remove this DCHECK when it does.
4633 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274634 if (_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:134635 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4636 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4637 }
4638 [self updateVoiceSearchBarVisibilityAnimated:NO];
4639
Mohamad Ahmadi7d09ec32017-07-11 22:32:194640 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134641
4642 [self tabSelected:newTab];
4643 DCHECK_EQ(newTab, [model currentTab]);
4644 [self installDelegatesForTab:newTab];
4645}
4646
4647// Observer method, tab changed.
4648- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4649 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4650 if (tab == [_model currentTab]) {
4651 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134652 }
4653}
4654
sdefresne49cf2862017-03-15 13:46:144655// Observer method, tab replaced.
4656- (void)tabModel:(TabModel*)model
4657 didReplaceTab:(Tab*)oldTab
4658 withTab:(Tab*)newTab
4659 atIndex:(NSUInteger)index {
4660 [self uninstallDelegatesForTab:oldTab];
4661 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264662
michaeldo79909fb2017-05-09 23:42:504663 if (_infoBarContainer) {
4664 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4665 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4666 }
4667
kkhorimotofa0844cc2017-03-20 17:01:264668 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4669 if (self.active && model.currentTab == newTab)
4670 [self displayTab:newTab isNewSelection:NO];
sdefresne49cf2862017-03-15 13:46:144671}
4672
sdefresnee65fd872016-12-19 13:38:134673// A tab has been removed, remove its views from display if necessary.
4674- (void)tabModel:(TabModel*)model
4675 didRemoveTab:(Tab*)tab
4676 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144677 [self uninstallDelegatesForTab:tab];
4678
kkhorimoto496fdd72017-06-12 19:56:314679 // Cancel dialogs for |tab|'s WebState.
4680 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4681
sdefresnee65fd872016-12-19 13:38:134682 // Remove stored native controllers for the tab.
4683 [_nativeControllersForTabIDs removeObjectForKey:tab.tabId];
4684
4685 // Ignore changes while the tab stack view is visible (or while suspended).
4686 // The display will be refreshed when this view becomes active again.
4687 if (!self.visible || !model.webUsageEnabled)
4688 return;
4689
4690 // Remove the find bar for now.
4691 [self hideFindBarWithAnimation:NO];
4692}
4693
4694- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4695 if (tab == [model currentTab]) {
4696 [_contentArea displayContentView:nil];
4697 [_toolbarController selectedTabChanged];
4698 }
4699
Mohamad Ahmadi7d09ec32017-07-11 22:32:194700 [_paymentRequestManager stopTrackingWebState:tab.webState];
4701
sdefresnee65fd872016-12-19 13:38:134702 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4703 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194704 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134705 }
4706}
4707
4708// Called when the number of tabs changes. Update the toolbar accordingly.
4709- (void)tabModelDidChangeTabCount:(TabModel*)model {
4710 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134711 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134712}
4713
4714#pragma mark - Upgrade Detection
4715
4716- (void)showUpgrade:(UpgradeCenter*)center {
4717 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274718 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134719 NSString* tabId = tab.tabId;
4720 DCHECK([tab infoBarManager]);
4721 [center addInfoBarToManager:[tab infoBarManager] forTabId:tabId];
4722 }
4723}
4724
sdefresnee65fd872016-12-19 13:38:134725
4726#pragma mark - InfoBarControllerDelegate
4727
4728- (void)infoBarContainerStateChanged:(bool)isAnimating {
4729 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4730 DCHECK(infoBarContainerView);
4731 CGRect containerFrame = infoBarContainerView.frame;
4732 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4733 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4734 containerFrame.size.height = height;
4735 BOOL isViewVisible = self.visible;
4736 [UIView animateWithDuration:0.1
4737 animations:^{
4738 [infoBarContainerView setFrame:containerFrame];
4739 }
4740 completion:^(BOOL finished) {
4741 if (!isViewVisible)
4742 return;
4743 UIAccessibilityPostNotification(
4744 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4745 }];
4746}
4747
4748- (BOOL)shouldAutorotate {
4749 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4750 // Don't rotate if a voice search is being presented or dismissed. Once the
4751 // transition animations finish, only the Voice Search UIViewController's
4752 // |-shouldAutorotate| will be called.
4753 return NO;
4754 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4755 // Don't auto rotate if side swipe controller view says not to.
4756 return NO;
4757 } else {
4758 return [super shouldAutorotate];
4759 }
4760}
4761
4762// Always return yes, as this tap should work with various recognizers,
4763// including UITextTapRecognizer, UILongPressGestureRecognizer,
4764// UIScrollViewPanGestureRecognizer and others.
4765- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4766 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4767 (UIGestureRecognizer*)otherGestureRecognizer {
4768 return YES;
4769}
4770
4771// Tap gestures should only be recognized within |_contentArea|.
4772- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4773 CGPoint location = [gesture locationInView:self.view];
4774
4775 // Only allow touches on descendant views of |_contentArea|.
4776 UIView* hitView = [self.view hitTest:location withEvent:nil];
4777 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4778}
4779
4780#pragma mark - SideSwipeController Delegate Methods
4781
4782- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4783 DCHECK(!IsIPadIdiom());
4784 // Update frame incase orientation changed while |_contentArea| was out of
4785 // the view hierarchy.
4786 [_contentArea setFrame:[sideSwipeView frame]];
4787
4788 [self.view insertSubview:_contentArea atIndex:0];
4789 [self updateVoiceSearchBarVisibilityAnimated:NO];
4790 [self updateToolbar];
4791
4792 // Reset horizontal stack view.
4793 [sideSwipeView removeFromSuperview];
4794 [_sideSwipeController setInSwipe:NO];
4795 [_infoBarContainer->view() setHidden:NO];
4796}
4797
4798- (UIView*)contentView {
4799 return _contentArea;
4800}
4801
4802- (TabStripController*)tabStripController {
4803 return _tabStripController;
4804}
4805
4806- (WebToolbarController*)toolbarController {
4807 return _toolbarController;
4808}
4809
4810- (BOOL)preventSideSwipe {
4811 if ([_toolbarController toolsPopupController])
4812 return YES;
4813
4814 if (_voiceSearchController && _voiceSearchController->IsVisible())
4815 return YES;
4816
sdefresnee65fd872016-12-19 13:38:134817 if (!self.active)
4818 return YES;
4819
4820 return NO;
4821}
4822
4823- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4824 if (visible) {
4825 [self updateVoiceSearchBarVisibilityAnimated:NO];
4826 [self updateToolbar];
4827 [_infoBarContainer->view() setHidden:NO];
4828 } else {
4829 // Hide UI accessories such as find bar and first visit overlays
4830 // for welcome page.
4831 [self hideFindBarWithAnimation:NO];
4832 [_infoBarContainer->view() setHidden:YES];
4833 [_voiceSearchBar setHidden:YES];
4834 }
4835}
4836
4837- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4838 BOOL seenToolbar = NO;
4839 BOOL seenInfoBarContainer = NO;
4840 BOOL seenContentArea = NO;
4841 for (UIView* view in views.subviews) {
4842 if (view == [_toolbarController view])
4843 seenToolbar = YES;
4844 else if (view == _infoBarContainer->view())
4845 seenInfoBarContainer = YES;
4846 else if (view == _contentArea)
4847 seenContentArea = YES;
4848 if ((seenToolbar && !seenInfoBarContainer) ||
4849 (seenInfoBarContainer && !seenContentArea))
4850 return NO;
4851 }
4852 return YES;
4853}
4854
4855#pragma mark - PreloadControllerDelegate methods
4856
rohitraoeeb5293b2017-06-15 14:40:024857- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444858 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134859}
4860
rohitraoeeb5293b2017-06-15 14:40:024861- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
4862 return [self hasControllerForURL:url];
4863}
4864
sdefresnee65fd872016-12-19 13:38:134865#pragma mark - BookmarkBridgeMethods
4866
4867// If an added or removed bookmark is the same as the current url, update the
4868// toolbar so the star highlight is kept in sync.
4869- (void)bookmarkNodeModified:(const BookmarkNode*)node {
kkhorimotob110b262017-06-01 18:38:254870 if ([_model currentTab] &&
4871 node->url() == [_model currentTab].lastCommittedURL) {
sdefresnee65fd872016-12-19 13:38:134872 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:254873 }
sdefresnee65fd872016-12-19 13:38:134874}
4875
4876// If all bookmarks are removed, update the toolbar so the star highlight is
4877// kept in sync.
4878- (void)allBookmarksRemoved {
4879 [self updateToolbar];
4880}
4881
4882#pragma mark - ShareToDelegate methods
4883
4884- (void)shareDidComplete:(ShareTo::ShareResult)shareStatus
jife5fcd332017-03-16 15:14:584885 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:134886 // The shareTo dialog dismisses itself instead of through
4887 // |-dismissViewControllerAnimated:completion:| so we must reset the
4888 // presenting state here.
4889 self.presenting = NO;
4890 [self.dialogPresenter tryToPresent];
4891
4892 switch (shareStatus) {
4893 case ShareTo::SHARE_SUCCESS:
pinkerton07e27842017-03-02 15:29:024894 if ([message length]) {
4895 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:134896 [self showSnackbar:message];
pinkerton07e27842017-03-02 15:29:024897 }
sdefresnee65fd872016-12-19 13:38:134898 break;
4899 case ShareTo::SHARE_ERROR:
4900 [self showErrorAlert:IDS_IOS_SHARE_TO_ERROR_ALERT_TITLE
4901 message:IDS_IOS_SHARE_TO_ERROR_ALERT];
4902 break;
4903 case ShareTo::SHARE_NETWORK_FAILURE:
4904 [self showErrorAlert:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT_TITLE
4905 message:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT];
4906 break;
4907 case ShareTo::SHARE_SIGN_IN_FAILURE:
4908 [self showErrorAlert:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT_TITLE
4909 message:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT];
4910 break;
4911 case ShareTo::SHARE_CANCEL:
4912 case ShareTo::SHARE_UNKNOWN_RESULT:
4913 break;
4914 }
4915}
4916
4917- (void)passwordAppExDidFinish:(ShareTo::ShareResult)shareStatus
4918 username:(NSString*)username
4919 password:(NSString*)password
jife5fcd332017-03-16 15:14:584920 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:134921 switch (shareStatus) {
4922 case ShareTo::SHARE_SUCCESS: {
4923 PasswordController* passwordController =
4924 [[_model currentTab] passwordController];
4925 __block BOOL shown = NO;
4926 [passwordController findAndFillPasswordForms:username
4927 password:password
4928 completionHandler:^(BOOL completed) {
4929 if (shown || !completed || ![message length])
4930 return;
pinkerton07e27842017-03-02 15:29:024931 TriggerHapticFeedbackForNotification(
4932 UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:134933 [self showSnackbar:message];
4934 shown = YES;
4935 }];
4936 break;
4937 }
4938 default:
4939 break;
4940 }
4941}
4942
4943- (void)showErrorAlert:(int)titleMessageId message:(int)messageId {
4944 NSString* title = l10n_util::GetNSString(titleMessageId);
4945 NSString* message = l10n_util::GetNSString(messageId);
4946 [self showErrorAlertWithStringTitle:title message:message];
4947}
4948
4949- (void)showErrorAlertWithStringTitle:(NSString*)title
4950 message:(NSString*)message {
4951 // Dismiss current alert.
4952 [_alertCoordinator stop];
4953
stkhapuginc9eee7b2017-04-10 15:49:274954 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
4955 message:message
4956 viewController:self];
sdefresnee65fd872016-12-19 13:38:134957 [_alertCoordinator start];
4958}
4959
4960- (void)showSnackbar:(NSString*)message {
4961 [_dependencyFactory showSnackbarWithMessage:message];
4962}
4963
4964#pragma mark - Show Mail Composer methods
4965
4966- (void)showMailComposer:(id)sender {
4967 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
4968 if (![MFMailComposeViewController canSendMail]) {
4969 NSString* alertTitle =
4970 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
4971 NSString* alertMessage =
4972 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
4973 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
4974 return;
4975 }
stkhapuginc9eee7b2017-04-10 15:49:274976 MFMailComposeViewController* mailViewController =
4977 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:134978 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
4979 [mailViewController setToRecipients:[command toRecipients]];
4980 [mailViewController setSubject:[command subject]];
4981 [mailViewController setMessageBody:[command body] isHTML:NO];
4982
4983 const base::FilePath& textFile = [command textFileToAttach];
4984 if (!textFile.empty()) {
4985 NSString* filename = base::SysUTF8ToNSString(textFile.value());
4986 NSData* data = [NSData dataWithContentsOfFile:filename];
4987 if (data) {
4988 NSString* displayName =
4989 base::SysUTF8ToNSString(textFile.BaseName().value());
4990 [mailViewController addAttachmentData:data
4991 mimeType:@"text/plain"
4992 fileName:displayName];
4993 }
4994 }
4995
4996 [mailViewController setMailComposeDelegate:self];
4997 [self presentViewController:mailViewController animated:YES completion:nil];
4998}
4999
5000#pragma mark - MFMailComposeViewControllerDelegate methods
5001
5002- (void)mailComposeController:(MFMailComposeViewController*)controller
5003 didFinishWithResult:(MFMailComposeResult)result
5004 error:(NSError*)error {
5005 [self dismissViewControllerAnimated:YES completion:nil];
5006}
5007
5008#pragma mark - StoreKitLauncher methods
5009
5010- (void)productViewControllerDidFinish:
5011 (SKStoreProductViewController*)viewController {
5012 [self dismissViewControllerAnimated:YES completion:nil];
5013}
5014
5015- (void)openAppStore:(NSString*)appId {
5016 if (![appId length])
5017 return;
5018 NSDictionary* product =
5019 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275020 SKStoreProductViewController* storeViewController =
5021 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135022 [storeViewController setDelegate:self];
5023 [storeViewController loadProductWithParameters:product completionBlock:nil];
5024 [self presentViewController:storeViewController animated:YES completion:nil];
5025}
5026
5027#pragma mark - TabDialogDelegate methods
5028
sdefresnee65fd872016-12-19 13:38:135029- (void)cancelDialogForTab:(Tab*)tab {
5030 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5031}
5032
5033#pragma mark - FKFeedbackPromptDelegate methods
5034
5035- (void)userTappedRateApp:(UIView*)view {
5036 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275037 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135038}
5039
5040- (void)userTappedSendFeedback:(UIView*)view {
5041 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275042 _rateThisAppDialog = nil;
5043 GenericChromeCommand* command =
5044 [[GenericChromeCommand alloc] initWithTag:IDC_REPORT_AN_ISSUE];
sdefresnee65fd872016-12-19 13:38:135045 [self chromeExecuteCommand:command];
5046}
5047
5048- (void)userTappedDismiss:(UIView*)view {
5049 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275050 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135051}
5052
5053#pragma mark - VoiceSearchBarDelegate
5054
5055- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275056 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135057 [self ensureVoiceSearchControllerCreated];
5058 return _voiceSearchController->IsTextToSpeechEnabled() &&
5059 _voiceSearchController->IsTextToSpeechSupported();
5060}
5061
5062- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275063 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135064 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5065}
5066
5067#pragma mark - VoiceSearchPresenter
5068
5069- (UIView*)voiceSearchButton {
5070 return _voiceSearchButton;
5071}
5072
5073- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5074 return [self currentLogoAnimationControllerOwner];
5075}
5076
5077@end