blob: f9c0d98cb20e905a94d91ae4521d314f2dc2ea45 [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"
gambard9efce7a2017-02-09 18:53:1719#include "base/files/file_path.h"
sdefresnee65fd872016-12-19 13:38:1320#include "base/format_macros.h"
21#include "base/i18n/rtl.h"
22#include "base/ios/block_types.h"
23#include "base/ios/ios_util.h"
sdefresnee65fd872016-12-19 13:38:1324#include "base/logging.h"
25#include "base/mac/bind_objc_block.h"
26#include "base/mac/bundle_locations.h"
27#include "base/mac/foundation_util.h"
sdefresnee65fd872016-12-19 13:38:1328#include "base/macros.h"
29#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2930#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1331#include "base/metrics/user_metrics.h"
32#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1333#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3934#include "base/strings/utf_string_conversions.h"
tzik14236032017-02-15 06:41:0135#include "base/threading/sequenced_worker_pool.h"
sdefresnee65fd872016-12-19 13:38:1336#include "components/bookmarks/browser/base_bookmark_model_observer.h"
37#include "components/bookmarks/browser/bookmark_model.h"
gambardbdc07cc2017-02-03 16:43:1138#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1339#include "components/infobars/core/infobar_manager.h"
40#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5441#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1342#include "components/search_engines/search_engines_pref_names.h"
43#include "components/search_engines/template_url_service.h"
44#include "components/sessions/core/tab_restore_service_helper.h"
45#include "components/strings/grit/components_strings.h"
46#include "components/toolbar/toolbar_model_impl.h"
47#include "ios/chrome/app/tests_hook.h"
48#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
49#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
50#include "ios/chrome/browser/chrome_url_constants.h"
51#include "ios/chrome/browser/chrome_url_util.h"
gambardd2e44fb2017-01-25 09:14:2152#import "ios/chrome/browser/content_suggestions/content_suggestions_coordinator.h"
sdefresnee65fd872016-12-19 13:38:1353#include "ios/chrome/browser/experimental_flags.h"
54#import "ios/chrome/browser/favicon/favicon_loader.h"
55#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
56#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
57#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3658#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1359#include "ios/chrome/browser/first_run/first_run.h"
60#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
61#include "ios/chrome/browser/infobars/infobar_container_ios.h"
62#include "ios/chrome/browser/infobars/infobar_container_view.h"
63#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
64#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
65#import "ios/chrome/browser/native_app_launcher/native_app_navigation_controller.h"
66#import "ios/chrome/browser/open_url_util.h"
67#import "ios/chrome/browser/passwords/password_controller.h"
sdefresnee65fd872016-12-19 13:38:1368#include "ios/chrome/browser/pref_names.h"
olivierrobin013ba672017-03-01 21:16:2469#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1370#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
71#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
72#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
73#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
74#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
75#import "ios/chrome/browser/snapshots/snapshot_cache.h"
76#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
77#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
pkld6e73e52017-03-08 15:56:5178#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:2879#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1380#import "ios/chrome/browser/tabs/tab.h"
81#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:1982#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:1383#import "ios/chrome/browser/tabs/tab_model.h"
84#import "ios/chrome/browser/tabs/tab_model_observer.h"
85#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
jif4a8cf942017-02-03 12:05:2486#import "ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h"
sdefresnee65fd872016-12-19 13:38:1387#import "ios/chrome/browser/ui/activity_services/share_protocol.h"
88#import "ios/chrome/browser/ui/activity_services/share_to_data.h"
jif4a8cf942017-02-03 12:05:2489#import "ios/chrome/browser/ui/activity_services/share_to_data_builder.h"
sdefresnee65fd872016-12-19 13:38:1390#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
91#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
92#import "ios/chrome/browser/ui/background_generator.h"
93#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
94#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:1395#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
96#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
97#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
98#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
99#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
100#import "ios/chrome/browser/ui/commands/open_url_command.h"
101#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
102#import "ios/chrome/browser/ui/commands/show_mail_composer_command.h"
103#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
104#import "ios/chrome/browser/ui/contextual_search/contextual_search_controller.h"
105#import "ios/chrome/browser/ui/contextual_search/contextual_search_mask_view.h"
106#import "ios/chrome/browser/ui/contextual_search/contextual_search_metrics.h"
107#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_protocols.h"
108#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
109#import "ios/chrome/browser/ui/contextual_search/touch_to_search_permissions_mediator.h"
110#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
111#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
112#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
113#import "ios/chrome/browser/ui/external_file_controller.h"
114#import "ios/chrome/browser/ui/external_file_remover.h"
115#include "ios/chrome/browser/ui/file_locations.h"
116#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
117#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
118#import "ios/chrome/browser/ui/fullscreen_controller.h"
119#import "ios/chrome/browser/ui/history/tab_history_cell.h"
120#import "ios/chrome/browser/ui/key_commands_provider.h"
sdefresnee65fd872016-12-19 13:38:13121#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
122#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_panel_view_controller.h"
123#include "ios/chrome/browser/ui/omnibox/page_info_model.h"
124#import "ios/chrome/browser/ui/omnibox/page_info_view_controller.h"
125#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
126#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37127#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13128#import "ios/chrome/browser/ui/preload_controller.h"
129#import "ios/chrome/browser/ui/preload_controller_delegate.h"
130#import "ios/chrome/browser/ui/print/print_controller.h"
131#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
132#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03133#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13134#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13135#include "ios/chrome/browser/ui/rtl_geometry.h"
136#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
137#import "ios/chrome/browser/ui/stack_view/card_view.h"
138#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
139#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
140#import "ios/chrome/browser/ui/sync/sync_util.h"
141#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
142#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
143#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
144#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
145#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sdefresnee65fd872016-12-19 13:38:13146#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
147#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
148#include "ios/chrome/browser/ui/ui_util.h"
149#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28150#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13151#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
152#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51153#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13154#import "ios/chrome/browser/web/error_page_content.h"
155#import "ios/chrome/browser/web/passkit_dialog_provider.h"
eugenebutcae3d9e62017-01-27 20:01:05156#import "ios/chrome/browser/web/repost_form_tab_helper.h"
sdefresne62a00bb2017-04-10 15:36:05157#import "ios/chrome/browser/web_state_list/web_state_list.h"
158#import "ios/chrome/browser/web_state_list/web_state_opener.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/xcallback_parameters.h"
160#import "ios/chrome/common/material_timing.h"
161#include "ios/chrome/grit/ios_chromium_strings.h"
162#include "ios/chrome/grit/ios_strings.h"
163#import "ios/net/request_tracker.h"
164#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
165#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
166#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
167#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
168#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
169#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
170#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
171#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
justincohen75011c32017-04-28 16:31:39172#import "ios/shared/chrome/browser/ui/commands/command_dispatcher.h"
sczs206ca2c2017-04-13 16:37:28173#import "ios/shared/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13174#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13175#include "ios/web/public/navigation_item.h"
176#import "ios/web/public/navigation_manager.h"
177#include "ios/web/public/referrer_util.h"
178#include "ios/web/public/ssl_status.h"
179#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39180#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13181#include "ios/web/public/web_client.h"
182#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13183#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29184#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13185#include "ios/web/public/web_state/web_state.h"
186#import "ios/web/public/web_state/web_state_delegate_bridge.h"
187#include "ios/web/public/web_thread.h"
188#import "ios/web/web_state/ui/crw_web_controller.h"
189#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17190#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13191#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
192#include "net/ssl/ssl_info.h"
193#include "net/url_request/url_request_context_getter.h"
194#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
195#include "ui/base/l10n/l10n_util.h"
196#include "ui/base/l10n/l10n_util_mac.h"
197#include "ui/base/page_transition_types.h"
198#include "url/gurl.h"
199
stkhapuginf58b10d02017-04-10 13:36:17200#if !defined(__has_feature) || !__has_feature(objc_arc)
201#error "This file requires ARC support."
202#endif
203
sdefresnee65fd872016-12-19 13:38:13204using base::UserMetricsAction;
205using bookmarks::BookmarkNode;
206
207class BrowserBookmarkModelBridge;
208class InfoBarContainerDelegateIOS;
209
210namespace ios_internal {
211NSString* const kPageInfoWillShowNotification =
212 @"kPageInfoWillShowNotification";
213NSString* const kPageInfoWillHideNotification =
214 @"kPageInfoWillHideNotification";
215NSString* const kLocationBarBecomesFirstResponderNotification =
216 @"kLocationBarBecomesFirstResponderNotification";
217NSString* const kLocationBarResignsFirstResponderNotification =
218 @"kLocationBarResignsFirstResponderNotification";
219} // namespace ios_internal
220
221namespace {
222
223typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
224 // Note: these values must match the ContextMenuOption enum in histograms.xml.
225 ACTION_OPEN_IN_NEW_TAB = 0,
226 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
227 ACTION_COPY_LINK_ADDRESS = 2,
228 ACTION_SAVE_IMAGE = 6,
229 ACTION_OPEN_IMAGE = 7,
230 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
231 ACTION_SEARCH_BY_IMAGE = 11,
232 ACTION_OPEN_JAVASCRIPT = 21,
233 ACTION_READ_LATER = 22,
234 NUM_ACTIONS = 23,
235};
236
237void Record(NSInteger action, bool is_image, bool is_link) {
238 if (is_image) {
239 if (is_link) {
240 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
241 NUM_ACTIONS);
242 } else {
243 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
244 NUM_ACTIONS);
245 }
246 } else {
247 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
248 NUM_ACTIONS);
249 }
250}
251
sdefresnee65fd872016-12-19 13:38:13252const CGFloat kVoiceSearchBarHeight = 59.0;
253
254// Dimensions to use when downsizing an image for search-by-image.
255const CGFloat kSearchByImageMaxImageArea = 90000.0;
256const CGFloat kSearchByImageMaxImageWidth = 600.0;
257const CGFloat kSearchByImageMaxImageHeight = 400.0;
258
259// The delay, in seconds, after startup before cleaning up the files received
260// from other applications that are not bookmarked nor referenced by an open or
261// recently closed tab.
262const int kExternalFilesCleanupDelaySeconds = 60;
263
264enum HeaderBehaviour {
265 // The header moves completely out of the screen.
266 Hideable = 0,
267 // This header stays on screen and doesn't overlap with the content.
268 Visible,
269 // This header stay on screen and covers part of the content.
270 Overlap
271};
272
sdefresnee65fd872016-12-19 13:38:13273const CGFloat kIPadFindBarOverlap = 11;
274
275bool IsURLAllowedInIncognito(const GURL& url) {
276 // Most URLs are allowed in incognito; the following are exceptions.
277 if (!url.SchemeIs(kChromeUIScheme))
278 return true;
279 std::string url_host = url.host();
280 return url_host != kChromeUIHistoryHost &&
281 url_host != kChromeUIHistoryFrameHost;
282}
283
284// Temporary key to use when storing native controllers vended to tabs before
285// they are added to the tab model.
286NSString* const kNativeControllerTemporaryKey = @"NativeControllerTemporaryKey";
287
rohitrao005a6432017-03-16 20:52:42288} // namespace
sdefresnee65fd872016-12-19 13:38:13289
stkhapugin952ecef2017-04-11 12:11:45290#pragma mark - HeaderDefinition helper
291
292@interface HeaderDefinition : NSObject
293
294// The header view.
295@property(nonatomic, strong) UIView* view;
296// How to place the view, and its behaviour when the headers move.
297@property(nonatomic, assign) HeaderBehaviour behaviour;
298// Reduces the height of a header to adjust for shadows.
299@property(nonatomic, assign) CGFloat heightAdjustement;
300// Nudges that particular header up by this number of points.
301@property(nonatomic, assign) CGFloat inset;
302
303- (instancetype)initWithView:(UIView*)view
304 headerBehaviour:(HeaderBehaviour)behaviour
305 heightAdjustment:(CGFloat)heightAdjustment
306 inset:(CGFloat)inset;
307
308+ (instancetype)definitionWithView:(UIView*)view
309 headerBehaviour:(HeaderBehaviour)behaviour
310 heightAdjustment:(CGFloat)heightAdjustment
311 inset:(CGFloat)inset;
312
313@end
314
315@implementation HeaderDefinition
316@synthesize view = _view;
317@synthesize behaviour = _behaviour;
318@synthesize heightAdjustement = _heightAdjustement;
319@synthesize inset = _inset;
320
321+ (instancetype)definitionWithView:(UIView*)view
322 headerBehaviour:(HeaderBehaviour)behaviour
323 heightAdjustment:(CGFloat)heightAdjustment
324 inset:(CGFloat)inset {
325 return [[self alloc] initWithView:view
326 headerBehaviour:behaviour
327 heightAdjustment:heightAdjustment
328 inset:inset];
329}
330
331- (instancetype)initWithView:(UIView*)view
332 headerBehaviour:(HeaderBehaviour)behaviour
333 heightAdjustment:(CGFloat)heightAdjustment
334 inset:(CGFloat)inset {
335 self = [super init];
336 if (self) {
337 _view = view;
338 _behaviour = behaviour;
339 _heightAdjustement = heightAdjustment;
340 _inset = inset;
341 }
342 return self;
343}
344
345@end
346
347#pragma mark - BVC
348
sdefresnee65fd872016-12-19 13:38:13349@interface BrowserViewController ()<AppRatingPromptDelegate,
350 ContextualSearchControllerDelegate,
351 ContextualSearchPanelMotionObserver,
352 CRWNativeContentProvider,
353 CRWWebStateDelegate,
354 DialogPresenterDelegate,
355 FullScreenControllerDelegate,
356 KeyCommandsPlumbing,
357 MFMailComposeViewControllerDelegate,
358 NewTabPageControllerObserver,
359 OverscrollActionsControllerDelegate,
360 PassKitDialogProvider,
361 PreloadControllerDelegate,
362 ShareToDelegate,
363 SKStoreProductViewControllerDelegate,
364 SnapshotOverlayProvider,
365 StoreKitLauncher,
366 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19367 TabHeadersDelegate,
sdefresnee65fd872016-12-19 13:38:13368 TabModelObserver,
369 TabSnapshottingDelegate,
370 UIGestureRecognizerDelegate,
371 UpgradeCenterClientProtocol,
372 VoiceSearchBarDelegate,
373 VoiceSearchBarOwner> {
374 // The dependency factory passed on initialization. Used to vend objects used
375 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27376 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13377
378 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27379 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13380
381 // Facade objects used by |_toolbarController|.
382 // Must outlive |_toolbarController|.
383 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
384 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
385
386 // Preload controller. Must outlive |_toolbarController|.
stkhapuginc9eee7b2017-04-10 15:49:27387 PreloadController* _preloadController;
sdefresnee65fd872016-12-19 13:38:13388
389 // The WebToolbarController used to display the omnibox.
stkhapuginc9eee7b2017-04-10 15:49:27390 WebToolbarController* _toolbarController;
sdefresnee65fd872016-12-19 13:38:13391
392 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27393 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13394
395 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27396 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13397
398 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27399 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13400
401 // Handles presentation of JavaScript dialogs.
402 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
403
justincohen75011c32017-04-28 16:31:39404 // Handles command dispatching.
405 CommandDispatcher* _dispatcher;
406
sdefresnee65fd872016-12-19 13:38:13407 // Keyboard commands provider. It offloads most of the keyboard commands
408 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27409 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13410
411 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
412 // to |-reparentToolbarController| will reset it to NO.
413 BOOL _isToolbarControllerRelinquished;
414
415 // The controller that owns the currently relinquished toolbar controller.
416 // The reference is weak because it's possible for the toolbar owner to be
417 // deallocated mid-animation due to memory pressure or a tab being closed
418 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27419 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13420
421 // Always present on tablet; always nil on phone.
stkhapuginc9eee7b2017-04-10 15:49:27422 TabStripController* _tabStripController;
sdefresnee65fd872016-12-19 13:38:13423
424 // The contextual search controller.
stkhapuginc9eee7b2017-04-10 15:49:27425 ContextualSearchController* _contextualSearchController;
sdefresnee65fd872016-12-19 13:38:13426
427 // The contextual search panel (always a subview of |self.view| if it exists).
428 ContextualSearchPanelView* _contextualSearchPanel;
429
430 // The contextual search mask (always a subview of |self.view| if it exists).
431 ContextualSearchMaskView* _contextualSearchMask;
432
433 // Used to inject Javascript implementing the PaymentRequest API and to
434 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27435 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13436
437 // Used to display the Page Info UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27438 PageInfoViewController* _pageInfoController;
sdefresnee65fd872016-12-19 13:38:13439
440 // Used to display the Voice Search UI. Nil if not visible.
441 scoped_refptr<VoiceSearchController> _voiceSearchController;
442
443 // Used to display the QR Scanner UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27444 QRScannerViewController* _qrScannerViewController;
sdefresnee65fd872016-12-19 13:38:13445
gambard6299cc1d2017-02-21 13:06:03446 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27447 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03448
gambardd2e44fb2017-01-25 09:14:21449 // Used to display the Suggestions.
stkhapuginc9eee7b2017-04-10 15:49:27450 ContentSuggestionsCoordinator* _contentSuggestionsCoordinator;
gambardd2e44fb2017-01-25 09:14:21451
sdefresnee65fd872016-12-19 13:38:13452 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27453 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13454
sdefresnee65fd872016-12-19 13:38:13455 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27456 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13457
458 // Records the set of domains for which full screen alert has already been
459 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27460 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13461
462 // Adapter to let BVC be the delegate for WebState.
463 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
464
465 // YES if new tab is animating in.
466 BOOL _inNewTabAnimation;
467
468 // YES if Voice Search should be started when the new tab animation is
469 // finished.
470 BOOL _startVoiceSearchAfterNewTabAnimation;
471
472 // YES if the user interacts with the location bar.
473 BOOL _locationBarHasFocus;
474 // YES if a load was cancelled due to typing in the location bar.
475 BOOL _locationBarEditCancelledLoad;
476 // YES if waiting for a foreground tab due to expectNewForegroundTab.
477 BOOL _expectingForegroundTab;
478
479 // The ChromeBrowserState associated with this BVC.
480 ios::ChromeBrowserState* _browserState; // weak
481
482 // Whether or not Incognito* is enabled.
483 BOOL _isOffTheRecord;
484
485 // The last point within |_contentArea| that's received a touch.
486 CGPoint _lastTapPoint;
487
488 // The time at which |_lastTapPoint| was most recently set.
489 CFTimeInterval _lastTapTime;
490
491 // A single infobar container handles all infobars in all tabs. It keeps
492 // track of infobars for current tab (accessed via infobar helper of
493 // the current tab).
494 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
495
496 // Bridge class to deliver container change notifications to BVC.
497 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
498
499 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37500 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27501 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13502
503 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11504 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13505
506 // Card side swipe view.
stkhapuginc9eee7b2017-04-10 15:49:27507 CardSideSwipeView* _sideSwipeView;
sdefresnee65fd872016-12-19 13:38:13508
sdefresnee65fd872016-12-19 13:38:13509 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27510 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13511
512 // Bridge to register for bookmark changes.
513 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
514
515 // Cached pointer to the bookmarks model.
516 bookmarks::BookmarkModel* _bookmarkModel; // weak
517
518 // The controller that shows the bookmarking UI after the user taps the star
519 // button.
stkhapuginc9eee7b2017-04-10 15:49:27520 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13521
522 // Used to remove unreferenced external files.
523 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
524
525 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27526 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13527
528 // Maps tab IDs to the most recent native content controller vended to that
529 // tab's web controller.
stkhapuginc9eee7b2017-04-10 15:49:27530 NSMapTable* _nativeControllersForTabIDs;
sdefresnee65fd872016-12-19 13:38:13531
532 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27533 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13534
535 // The sender for the last received IDC_VOICE_SEARCH command.
stkhapuginc9eee7b2017-04-10 15:49:27536 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13537
538 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27539 AlertCoordinator* _alertCoordinator;
sdefresnee65fd872016-12-19 13:38:13540}
541
542// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17543@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13544// The browser's preload controller.
stkhapuginf58b10d02017-04-10 13:36:17545@property(nonatomic, strong, readonly) PreloadController* preloadController;
sdefresnee65fd872016-12-19 13:38:13546// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17547@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13548// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17549@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13550// Whether the current tab can enable the reader mode menu item.
551@property(nonatomic, assign, readonly) BOOL canUseReaderMode;
552// Whether the current tab can enable the request desktop menu item.
553@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
554// Whether the sharing menu should be enabled.
555@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
556// Helper method to check web controller canShowFindBar method.
557@property(nonatomic, assign, readonly) BOOL canShowFindBar;
558// Whether the controller's view is currently available.
559// YES from viewWillAppear to viewWillDisappear.
560@property(nonatomic, assign, getter=isVisible) BOOL visible;
561// Whether the controller's view is currently visible.
562// YES from viewDidAppear to viewWillDisappear.
563@property(nonatomic, assign) BOOL viewVisible;
564// Whether the controller is currently dismissing a presented view controller.
565@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
566// Returns YES if the toolbar has not been scrolled out by fullscreen.
567@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
568 BOOL toolbarOnScreen;
569// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12570@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13571// Whether BVC prefers to hide the status bar. This value is used to determine
572// the response from the |prefersStatusBarHidden| method.
573@property(nonatomic, assign) BOOL hideStatusBar;
574// Whether the VoiceSearchBar should be displayed.
575@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
576// Coordinator for displaying a modal overlay with activity indicator to prevent
577// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17578@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13579 ActivityOverlayCoordinator* activityOverlayCoordinator;
580
liaoyukeea9f3ee62017-03-07 22:05:39581// The user agent type used to load the currently visible page. User agent type
582// is NONE if there is no visible page or visible page is a native page.
583@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
584
stkhapugin952ecef2017-04-11 12:11:45585// Returns the header views, all the chrome on top of the page, including the
586// ones that cannot be scrolled off screen by full screen.
587@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
588
sdefresnee65fd872016-12-19 13:38:13589// BVC initialization:
590// If the BVC is initialized with a valid browser state & tab model immediately,
591// the path is straightforward: functionality is enabled, and the UI is built
592// when -viewDidLoad is called.
593// If the BVC is initialized without a browser state or tab model, the tab model
594// and browser state may or may not be provided before -viewDidLoad is called.
595// In most cases, they will not, to improve startup performance.
596// In order to handle this, initialization of various aspects of BVC have been
597// broken out into the following functions, which have expectations (enforced
598// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
599
600// Registers for notifications.
601- (void)registerForNotifications;
602// Called when a tab is starting to load. If it's a link click or form
603// submission, the user is navigating away from any entries in the forward
604// history. Tell the toolbar so it can update the UI appropriately.
605// See the warning on [Tab webWillStartLoadingURL] about invocation of this
606// method sequence by malicious pages.
607- (void)pageLoadStarting:(NSNotification*)notify;
608// Called when a tab actually starts loading.
609- (void)pageLoadStarted:(NSNotification*)notify;
610// Called when a tab finishes loading. Update the Omnibox with the url and
611// stop any page load progess display.
612- (void)pageLoadComplete:(NSNotification*)notify;
613// Called when a tab is deselected in the model.
614// This notification also occurs when a tab is closed.
615- (void)tabDeselected:(NSNotification*)notify;
616// Animates sliding current tab and rotate-entering new tab while new tab loads
617// in background on the iPhone only.
618- (void)tabWasAdded:(NSNotification*)notify;
619
620// Updates non-view-related functionality with the given browser state and tab
621// model.
622// Does not matter whether or not the view has been loaded.
623- (void)updateWithTabModel:(TabModel*)model
624 browserState:(ios::ChromeBrowserState*)browserState;
625// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
626// the status bar to mimic this layout.
627- (void)installFakeStatusBar;
628// Builds the UI parts of tab strip and the toolbar. Does not matter whether
629// or not browser state and tab model are valid.
630- (void)buildToolbarAndTabStrip;
631// Updates view-related functionality with the given tab model and browser
632// state. The view must have been loaded. Uses |_browserState| and |_model|.
633- (void)addUIFunctionalityForModelAndBrowserState;
634// Sets the correct frame and heirarchy for subviews and helper views.
635- (void)setUpViewLayout;
636// Sets the correct frame for the tab strip based on the given maximum width.
637- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
638// Makes |tab| the currently visible tab, displaying its view. Calls
639// -selectedTabChanged on the toolbar only if |newSelection| is YES.
640- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
641// Initializes the bookmark interaction controller if not already initialized.
642- (void)initializeBookmarkInteractionController;
643
sdefresnee65fd872016-12-19 13:38:13644// Shows the tools menu popup.
645- (void)showToolsMenuPopup;
646// Add all delegates to the provided |tab|.
647- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14648// Remove delegates from the provided |tab|.
649- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13650// Closes the current tab, with animation if applicable.
651- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13652// Shows the menu to initiate sharing |data|.
653- (void)sharePageWithData:(ShareToData*)data;
654// Convenience method to share the current page.
655- (void)sharePage;
656// Prints the web page in the current tab.
657- (void)print;
658// Shows the Online Help Page in a tab.
659- (void)showHelpPage;
660// Show the bookmarks page.
661- (void)showAllBookmarks;
662// Shows a panel within the New Tab Page.
663- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel;
664// Shows the "rate this app" dialog.
665- (void)showRateThisAppDialog;
666// Dismisses the "rate this app" dialog.
667- (void)dismissRateThisAppDialog;
668#if !defined(NDEBUG)
669// Shows the source of the current page.
670- (void)viewSource;
671#endif
olivierrobin889af53f2017-03-01 14:56:32672// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13673- (BOOL)isTabNativePage:(Tab*)tab;
674// Returns the view to use when animating a page in or out, positioning it to
675// fill the content area but not actually adding it to the view hierarchy.
676- (UIImageView*)pageOpenCloseAnimationView;
677// Returns the view to use when animating full screen NTP paper in, filling the
678// entire screen but not actually adding it to the view hierarchy.
679- (UIImageView*)pageFullScreenOpenCloseAnimationView;
680// Updates the toolbar display based on the current tab.
681- (void)updateToolbar;
682// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12683// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13684- (void)updateDialogPresenterActiveState;
685// Dismisses popups and modal dialogs that are displayed above the BVC upon size
686// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
687// is performed.
688// TODO(crbug.com/522721): Support size changes for all popups and modal
689// dialogs.
690- (void)dismissPopups;
691// Create and show the find bar.
692- (void)initFindBarForTab;
693// Search for find bar query string.
694- (void)searchFindInPage;
695// Update find bar with model data. If |shouldFocus| is set to YES, the text
696// field will become first responder.
697- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
698// Close and disable find in page bar.
699- (void)closeFindInPage;
700// Hide find bar.
701- (void)hideFindBarWithAnimation:(BOOL)animate;
702// Shows find bar. If |selectText| is YES, all text inside the Find Bar
703// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
704// set to be first responder.
705- (void)showFindBarWithAnimation:(BOOL)animate
706 selectText:(BOOL)selectText
707 shouldFocus:(BOOL)shouldFocus;
708// Show the Page Security Info.
709- (void)showPageInfoPopupForView:(UIView*)sourceView;
710// Hide the Page Security Info.
711- (void)hidePageInfoPopupForView:(UIView*)sourceView;
712// Shows the tab history popup containing the tab's backward history.
713- (void)showTabHistoryPopupForBackwardHistory;
714// Shows the tab history popup containing the tab's forward history.
715- (void)showTabHistoryPopupForForwardHistory;
716// Navigate back/forward to the selected entry in the tab's history.
717- (void)navigateToSelectedEntry:(id)sender;
718// The infobar state (typically height) has changed.
719- (void)infoBarContainerStateChanged:(bool)is_animating;
720// Adds a CardView on top of the contentArea either taking the size of the full
721// screen or just the size of the space under the header.
722// Returns the CardView that was added.
723- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
724// Called when either a tab finishes loading or when a tab with finished content
725// is added directly to the model via pre-rendering. The tab must be non-nil and
726// must be a member of the tab model controlled by this BrowserViewController.
727- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
728// Evaluates Javascript asynchronously using the current page context.
729- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13730// Helper methods used by ShareToDelegate methods.
731// Shows an alert with the given title and message id.
732- (void)showErrorAlert:(int)titleMessageId message:(int)messageId;
733// Helper method displaying an alert with the given title and message.
734// Dismisses previous alert if it has not been dismissed yet.
735- (void)showErrorAlertWithStringTitle:(NSString*)title
736 message:(NSString*)message;
737// Shows a self-dismissing snackbar displaying |message|.
738- (void)showSnackbar:(NSString*)message;
739// Induces an intentional crash in the browser process.
740- (void)induceBrowserCrash;
741// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17742- (void)managePermissionAndSaveImage:(NSData*)data
743 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13744// Saves the image. In order to keep the metadata of the image, the image is
745// saved as a temporary file on disk then saved in photos.
746// This should be called on FILE thread.
gambard9efce7a2017-02-09 18:53:17747- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13748// Called when Chrome has been denied access to the photos or videos and the
749// user can change it.
750// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
751// settings. Dismiss previous alert if it has not been dismissed yet.
752- (void)displayImageErrorAlertWithSettingsOnMainQueue;
753// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
754// previous alert if it has not been dismissed yet.
755- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
756// Called when Chrome has been denied access to the photos or videos and the
757// user cannot change it.
758// Shows a privacy alert on the main queue, with errorContent as the message.
759// Dismisses previous alert if it has not been dismissed yet.
760- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
761// Called with the results of saving a picture in the photo album. If error is
762// nil the save succeeded.
763- (void)finishSavingImageWithError:(NSError*)error;
764// Provides a view that encompasses currently displayed infobar(s) or nil
765// if no infobar is presented.
766- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
767// Returns a vertical infobar offset relative to the tab content.
768- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
769// Provides a view that encompasses the voice search bar if it's displayed or
770// nil if the voice search bar isn't displayed.
771- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
772// Returns a vertical voice search bar offset relative to the tab content.
773- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
774// Lazily instantiates |_voiceSearchController|.
775- (void)ensureVoiceSearchControllerCreated;
776// Lazily instantiates |_voiceSearchBar| and adds it to the view.
777- (void)ensureVoiceSearchBarCreated;
778// Shows/hides the voice search bar.
779- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
780// The LogoAnimationControllerOwner to be used for the next logo transition
781// animation.
782- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13783// Returns the footer view if one exists (e.g. the voice search bar).
784- (UIView*)footerView;
785// Returns the height of the header view for the tab model's current tab.
786- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13787// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45788- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13789 atOffset:(CGFloat)headerOffset;
790// Returns the y coordinate for the footer's frame when animating the footer
791// in/out of fullscreen.
792- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
793// Called when the animation for setting the header view's offset is finished.
794// |completed| should indicate if the animation finished completely or was
795// interrupted. |offset| should indicate the header offset after the animation.
796// |dragged| should indicate if the header moved due to the user dragging.
797- (void)fullScreenController:(FullScreenController*)controller
798 headerAnimationCompleted:(BOOL)completed
799 offset:(CGFloat)offset;
800// Performs a search with the image at the given url. The referrer is used to
801// download the image.
802- (void)searchByImageAtURL:(const GURL&)url
803 referrer:(const web::Referrer)referrer;
804// Saves the image at the given URL on the system's album. The referrer is used
805// to download the image.
806- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
807
808// Determines the center of |sender| if it's a view or a toolbar item, and save
809// the CGPoint and timestamp.
810- (void)setLastTapPoint:(id)sender;
811// Get return the last stored |_lastTapPoint| if it's been set within the past
812// second.
813- (CGPoint)lastTapPoint;
814// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
815- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
816// Returns the native controller being used by |tab|'s web controller.
817- (id)nativeControllerForTab:(Tab*)tab;
818// Installs the BVC as overscroll actions controller of |nativeContent| if
819// needed. Sets the style of the overscroll actions toolbar.
820- (void)setOverScrollActionControllerToStaticNativeContent:
821 (StaticHtmlNativeContent*)nativeContent;
822// Whether the BVC should declare keyboard commands.
823- (BOOL)shouldRegisterKeyboardCommands;
824// Adds the given url to the reading list.
825- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
826@end
827
828class InfoBarContainerDelegateIOS
829 : public infobars::InfoBarContainer::Delegate {
830 public:
831 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
832 : controller_(controller) {}
833
834 ~InfoBarContainerDelegateIOS() override {}
835
836 private:
837 SkColor GetInfoBarSeparatorColor() const override {
838 NOTIMPLEMENTED();
839 return SK_ColorBLACK;
840 }
841
842 int ArrowTargetHeightForInfoBar(
843 size_t index,
844 const gfx::SlideAnimation& animation) const override {
845 return 0;
846 }
847
848 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
849 int arrow_target_height,
850 int bar_target_height,
851 int* arrow_height,
852 int* arrow_half_width,
853 int* bar_height) const override {
854 DCHECK_NE(-1, bar_target_height)
855 << "Infobars don't have a default height on iOS";
856 *arrow_height = 0;
857 *arrow_half_width = 0;
858 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
859 }
860
861 void InfoBarContainerStateChanged(bool is_animating) override {
862 [controller_ infoBarContainerStateChanged:is_animating];
863 }
864
865 bool DrawInfoBarArrows(int* x) const override { return false; }
866
stkhapuginf58b10d02017-04-10 13:36:17867 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13868};
869
870// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
871@interface BrowserViewController (BookmarkBridgeMethods)
872// If a bookmark matching the currentTab url is added or moved, update the
873// toolbar state so the star highlight is in sync.
874- (void)bookmarkNodeModified:(const BookmarkNode*)node;
875- (void)allBookmarksRemoved;
876@end
877
878// Handle notification that bookmarks has been removed changed so we can update
879// the bookmarked star icon.
880class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
881 public:
882 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
883 : owner_(owner) {}
884
885 ~BrowserBookmarkModelBridge() override {}
886
887 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
888 const BookmarkNode* parent,
889 int old_index,
890 const BookmarkNode* node,
891 const std::set<GURL>& removed_urls) override {
892 [owner_ bookmarkNodeModified:node];
893 }
894
895 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
896 bool ids_reassigned) override {}
897
898 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
899 const BookmarkNode* old_parent,
900 int old_index,
901 const BookmarkNode* new_parent,
902 int new_index) override {}
903
904 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
905 const BookmarkNode* parent,
906 int index) override {
907 [owner_ bookmarkNodeModified:parent->GetChild(index)];
908 }
909
910 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
911 const BookmarkNode* node) override {}
912
913 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
914 const BookmarkNode* node) override {}
915
916 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
917 const BookmarkNode* node) override {}
918
919 void BookmarkAllUserNodesRemoved(
920 bookmarks::BookmarkModel* model,
921 const std::set<GURL>& removed_urls) override {
922 [owner_ allBookmarksRemoved];
923 }
924
925 private:
stkhapuginf58b10d02017-04-10 13:36:17926 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13927};
928
929@implementation BrowserViewController
930
931@synthesize contentArea = _contentArea;
932@synthesize typingShield = _typingShield;
933@synthesize active = _active;
934@synthesize visible = _visible;
935@synthesize viewVisible = _viewVisible;
936@synthesize dismissingModal = _dismissingModal;
937@synthesize hideStatusBar = _hideStatusBar;
938@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
939@synthesize presenting = _presenting;
940
941#pragma mark - Object lifecycle
942
943- (instancetype)initWithTabModel:(TabModel*)model
944 browserState:(ios::ChromeBrowserState*)browserState
945 dependencyFactory:
946 (BrowserViewControllerDependencyFactory*)factory {
947 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
948 if (self) {
949 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17950
stkhapuginc9eee7b2017-04-10 15:49:27951 _dependencyFactory = factory;
952 _nativeControllersForTabIDs = [NSMapTable strongToWeakObjectsMapTable];
953 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
954 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39955 _dispatcher = [[CommandDispatcher alloc] init];
956 [_dispatcher startDispatchingToTarget:self
957 forProtocol:@protocol(UrlLoader)];
958 [_dispatcher startDispatchingToTarget:self
959 forProtocol:@protocol(WebToolbarDelegate)];
960 [_dispatcher startDispatchingToTarget:self
961 forSelector:@selector(chromeExecuteCommand:)];
962
sdefresnee65fd872016-12-19 13:38:13963 _javaScriptDialogPresenter.reset(
964 new JavaScriptDialogPresenterImpl(_dialogPresenter));
965 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
966 // TODO(leng): Delay this.
967 [[UpgradeCenter sharedInstance] registerClient:self];
968 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13969 if (model && browserState)
970 [self updateWithTabModel:model browserState:browserState];
971 if ([[NSUserDefaults standardUserDefaults]
972 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:27973 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:13974 }
975 }
976 return self;
977}
978
979- (instancetype)initWithNibName:(NSString*)nibNameOrNil
980 bundle:(NSBundle*)nibBundleOrNil {
981 NOTREACHED();
982 return nil;
983}
984
985- (instancetype)initWithCoder:(NSCoder*)aDecoder {
986 NOTREACHED();
987 return nil;
988}
989
990- (void)dealloc {
stkhapuginc9eee7b2017-04-10 15:49:27991 _tabStripController = nil;
992 _infoBarContainer = nil;
993 _readingListMenuNotifier = nil;
sdefresnedc432f42017-01-17 14:36:59994 if (_bookmarkModel)
995 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
sdefresnee65fd872016-12-19 13:38:13996 [_model removeObserver:self];
997 [[UpgradeCenter sharedInstance] unregisterClient:self];
998 [[NSNotificationCenter defaultCenter] removeObserver:self];
999 [_toolbarController setDelegate:nil];
stkhapuginc9eee7b2017-04-10 15:49:271000 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131001 _voiceSearchController->SetDelegate(nil);
1002 [_rateThisAppDialog setDelegate:nil];
1003 [_model closeAllTabs];
sdefresnee65fd872016-12-19 13:38:131004}
1005
1006#pragma mark - Accessibility
1007
1008- (BOOL)accessibilityPerformEscape {
1009 [self dismissPopups];
1010 return YES;
1011}
1012
1013#pragma mark - Properties
1014
sdefresnee65fd872016-12-19 13:38:131015- (void)setActive:(BOOL)active {
1016 if (_active == active) {
1017 return;
1018 }
1019 _active = active;
1020
1021 // If not active, display an activity indicator overlay over the view to
1022 // prevent interaction with the web page.
1023 // TODO(crbug.com/637093): This coordinator should be managed by the
1024 // coordinator used to present BrowserViewController, when implemented.
1025 if (active) {
1026 [self.activityOverlayCoordinator stop];
1027 self.activityOverlayCoordinator = nil;
1028 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171029 self.activityOverlayCoordinator =
1030 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131031 [self.activityOverlayCoordinator start];
1032 }
1033
1034 if (_browserState) {
1035 web::ActiveStateManager* active_state_manager =
1036 web::BrowserState::GetActiveStateManager(_browserState);
1037 active_state_manager->SetActive(active);
1038 }
1039
1040 [_model setWebUsageEnabled:active];
1041 [self updateDialogPresenterActiveState];
1042
1043 if (active) {
1044 // Make sure the tab (if any; it's possible to get here without a current
1045 // tab if the caller is about to create one) ends up on screen completely.
1046 Tab* currentTab = [_model currentTab];
1047 // Force loading the view in case it was not loaded yet.
1048 [self ensureViewCreated];
1049 if (_expectingForegroundTab)
1050 [currentTab.webController setOverlayPreviewMode:YES];
1051 if (currentTab)
1052 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341053 } else {
1054 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131055 }
1056 [_contextualSearchController enableContextualSearch:active];
1057 [_paymentRequestManager enablePaymentRequest:active];
1058
1059 [self setNeedsStatusBarAppearanceUpdate];
1060}
1061
1062- (void)setPrimary:(BOOL)primary {
1063 [_model setPrimary:primary];
1064 if (primary) {
1065 [self updateDialogPresenterActiveState];
1066 } else {
1067 self.dialogPresenter.active = false;
1068 }
1069}
1070
1071- (BOOL)isPlayingTTS {
1072 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1073}
1074
sdefresne6165c8742017-01-16 15:42:021075- (ios::ChromeBrowserState*)browserState {
1076 return _browserState;
1077}
1078
1079- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271080 return _model;
sdefresne6165c8742017-01-16 15:42:021081}
1082
sdefresnee65fd872016-12-19 13:38:131083- (SideSwipeController*)sideSwipeController {
1084 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271085 _sideSwipeController =
1086 [[SideSwipeController alloc] initWithTabModel:_model
1087 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131088 [_sideSwipeController setSnapshotDelegate:self];
1089 [_sideSwipeController setSwipeDelegate:self];
1090 }
1091 return _sideSwipeController;
1092}
1093
1094- (PreloadController*)preloadController {
stkhapuginc9eee7b2017-04-10 15:49:271095 return _preloadController;
sdefresnee65fd872016-12-19 13:38:131096}
1097
1098- (DialogPresenter*)dialogPresenter {
1099 return _dialogPresenter;
1100}
1101
1102- (BOOL)canUseReaderMode {
1103 Tab* tab = [_model currentTab];
1104 if ([self isTabNativePage:tab])
1105 return NO;
1106
1107 return [tab canSwitchToReaderMode];
1108}
1109
1110- (BOOL)canUseDesktopUserAgent {
1111 Tab* tab = [_model currentTab];
1112 if ([self isTabNativePage:tab])
1113 return NO;
1114
1115 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441116 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131117}
1118
1119// Whether the sharing menu should be shown.
1120- (BOOL)canShowShareMenu {
1121 Tab* tab = [_model currentTab];
1122 // TODO(shreyasv): Make it so the URL returned by the tab is always valid and
1123 // remove check on net::NSURLWithGURL(tab.url) ( http://crbug.com/400999 ).
1124 return tab && !tab.url.SchemeIs(kChromeUIScheme) &&
1125 net::NSURLWithGURL(tab.url);
1126}
1127
1128- (BOOL)canShowFindBar {
1129 // Make sure web controller can handle find in page.
1130 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421131 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131132 return NO;
rohitrao005a6432017-03-16 20:52:421133 }
sdefresnee65fd872016-12-19 13:38:131134
rohitrao005a6432017-03-16 20:52:421135 auto* helper = FindTabHelper::FromWebState(tab.webState);
1136 return (helper && helper->CurrentPageSupportsFindInPage() &&
1137 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131138}
1139
liaoyukeea9f3ee62017-03-07 22:05:391140- (web::UserAgentType)userAgentType {
1141 web::WebState* webState = [_model currentTab].webState;
1142 if (!webState)
1143 return web::UserAgentType::NONE;
1144 web::NavigationItem* visibleItem =
1145 webState->GetNavigationManager()->GetVisibleItem();
1146 if (!visibleItem)
1147 return web::UserAgentType::NONE;
1148
1149 return visibleItem->GetUserAgentType();
1150}
1151
sdefresnee65fd872016-12-19 13:38:131152- (void)setVisible:(BOOL)visible {
1153 if (_visible == visible)
1154 return;
1155 _visible = visible;
1156}
1157
1158- (void)setViewVisible:(BOOL)viewVisible {
1159 if (_viewVisible == viewVisible)
1160 return;
1161 _viewVisible = viewVisible;
1162 self.visible = viewVisible;
1163 [self updateDialogPresenterActiveState];
1164}
1165
1166- (BOOL)isToolbarOnScreen {
1167 return [self headerHeight] - [self currentHeaderOffset] > 0;
1168}
1169
kkhorimotoa44349c12017-04-12 23:02:121170- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1171 if (_inNewTabAnimation == inNewTabAnimation)
1172 return;
1173 _inNewTabAnimation = inNewTabAnimation;
1174 [self updateDialogPresenterActiveState];
1175}
1176
sdefresnee65fd872016-12-19 13:38:131177- (BOOL)isInNewTabAnimation {
1178 return _inNewTabAnimation;
1179}
1180
1181- (BOOL)shouldShowVoiceSearchBar {
1182 // On iPads, the voice search bar should only be shown for regular horizontal
1183 // size class configurations. It should always be shown for voice search
1184 // results Tabs on iPhones, including configurations with regular horizontal
1185 // size classes (i.e. landscape iPhone 6 Plus).
1186 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1187 UIUserInterfaceSizeClassCompact;
1188 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1189 (!IsIPadIdiom() || compactWidth);
1190}
1191
1192- (void)setHideStatusBar:(BOOL)hideStatusBar {
1193 if (_hideStatusBar == hideStatusBar)
1194 return;
1195 _hideStatusBar = hideStatusBar;
1196 [self setNeedsStatusBarAppearanceUpdate];
1197}
1198
1199#pragma mark - IBActions
1200
1201- (void)shieldWasTapped:(id)sender {
1202 [_toolbarController cancelOmniboxEdit];
1203}
1204
1205- (void)newTab:(id)sender {
1206 [self setLastTapPoint:sender];
1207 DCHECK(self.visible || self.dismissingModal);
1208 Tab* currentTab = [_model currentTab];
1209 if (currentTab) {
jif7fed8122017-02-08 13:15:251210 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
sdefresnee65fd872016-12-19 13:38:131211 }
1212 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1213 transition:ui::PAGE_TRANSITION_TYPED];
1214}
1215
1216#pragma mark - UIViewController methods
1217
1218// Perform additional set up after loading the view, typically from a nib.
1219- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281220 CGRect initialViewsRect = self.view.frame;
1221 initialViewsRect.origin.y += StatusBarHeight();
1222 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131223 UIViewAutoresizing initialViewAutoresizing =
1224 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1225
stkhapuginf58b10d02017-04-10 13:36:171226 self.contentArea =
1227 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131228 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171229 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131230 self.typingShield.autoresizingMask = initialViewAutoresizing;
1231 [self.typingShield addTarget:self
1232 action:@selector(shieldWasTapped:)
1233 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131234 self.view.autoresizingMask = initialViewAutoresizing;
1235 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1236 [self.view addSubview:self.contentArea];
1237 [self.view addSubview:self.typingShield];
1238 [super viewDidLoad];
1239
1240 // Install fake status bar for iPad iOS7
1241 [self installFakeStatusBar];
1242 [self buildToolbarAndTabStrip];
1243 [self setUpViewLayout];
1244 // If the tab model and browser state are valid, finish initialization.
1245 if (_model && _browserState)
1246 [self addUIFunctionalityForModelAndBrowserState];
1247
1248 // Add a tap gesture recognizer to save the last tap location for the source
1249 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271250 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1251 initWithTarget:self
1252 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131253 [tapRecognizer setDelegate:self];
1254 [tapRecognizer setCancelsTouchesInView:NO];
1255 [_contentArea addGestureRecognizer:tapRecognizer];
1256}
1257
1258- (void)viewDidAppear:(BOOL)animated {
1259 [super viewDidAppear:animated];
1260 self.viewVisible = YES;
1261 [self updateDialogPresenterActiveState];
1262}
1263
1264- (void)viewWillAppear:(BOOL)animated {
1265 [super viewWillAppear:animated];
1266
1267 // Reparent the toolbar if it's been relinquished.
1268 if (_isToolbarControllerRelinquished)
1269 [self reparentToolbarController];
1270
1271 self.visible = YES;
1272
1273 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251274 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131275 _infoBarContainer->RestoreInfobars();
1276 }
1277
1278 // If the controller is suspended, or has been paged out due to low memory,
1279 // updating the view will be handled when it's displayed again.
1280 if (![_model webUsageEnabled] || !self.contentArea)
1281 return;
1282 // Update the displayed tab (if any; the switcher may not have created one
1283 // yet) in case it changed while showing the switcher.
1284 Tab* currentTab = [_model currentTab];
1285 if (currentTab)
1286 [self displayTab:currentTab isNewSelection:YES];
1287}
1288
1289- (void)viewWillDisappear:(BOOL)animated {
1290 self.viewVisible = NO;
1291 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131292 [[_model currentTab] wasHidden];
1293 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251294 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131295 _infoBarContainer->SuspendInfobars();
1296 }
1297 [super viewWillDisappear:animated];
1298}
1299
1300- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1301 duration:(NSTimeInterval)duration {
1302 [super willRotateToInterfaceOrientation:orient duration:duration];
1303 [self dismissPopups];
1304 [self reshowFindBarIfNeededWithCoordinator:nil];
1305}
1306
1307- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1308 [super didRotateFromInterfaceOrientation:orient];
1309
1310 // This reinitializes the toolbar, including updating the Overlay View,
1311 // if there is one.
1312 [self updateToolbar];
1313 [self infoBarContainerStateChanged:false];
1314}
1315
1316- (BOOL)prefersStatusBarHidden {
1317 return self.hideStatusBar;
1318}
1319
1320// Called when in the foreground and the OS needs more memory. Release as much
1321// as possible.
1322- (void)didReceiveMemoryWarning {
1323 // Releases the view if it doesn't have a superview.
1324 [super didReceiveMemoryWarning];
1325
1326 // Release any cached data, images, etc that aren't in use.
1327 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1328 // but since the FaviconCache uses obj-c in the header, it can't be included
1329 // there.
1330 if (_browserState) {
1331 FaviconLoader* loader =
1332 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1333 _browserState);
1334 if (loader)
1335 loader->PurgeCache();
1336 }
1337
1338 if (![self isViewLoaded]) {
1339 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1340 // as the BrowserViewController.
1341 self.contentArea = nil;
1342 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271343 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131344 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271345 _contentSuggestionsCoordinator = nil;
1346 _qrScannerViewController = nil;
1347 _readingListCoordinator = nil;
1348 _toolbarController = nil;
1349 _toolbarModelDelegate = nil;
1350 _toolbarModelIOS = nil;
1351 _tabStripController = nil;
1352 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131353 }
1354}
1355
1356- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1357 [super traitCollectionDidChange:previousTraitCollection];
1358 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1359 // because in some cases the presented view controller isn't a child of the
1360 // BVC in the view controller hierarchy (some intervening object isn't a
1361 // view controller).
1362 [self.presentedViewController
1363 traitCollectionDidChange:previousTraitCollection];
1364 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1365 // Update voice search bar visibility.
1366 [self updateVoiceSearchBarVisibilityAnimated:NO];
1367}
1368
1369- (void)viewWillTransitionToSize:(CGSize)size
1370 withTransitionCoordinator:
1371 (id<UIViewControllerTransitionCoordinator>)coordinator {
1372 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1373 [self dismissPopups];
1374 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1375}
1376
1377- (void)reshowFindBarIfNeededWithCoordinator:
1378 (id<UIViewControllerTransitionCoordinator>)coordinator {
1379 if (![_findBarController isFindInPageShown])
1380 return;
1381
1382 // Record focused state.
1383 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1384
1385 [self hideFindBarWithAnimation:NO];
1386
stkhapuginc9eee7b2017-04-10 15:49:271387 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131388 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1389 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271390 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131391 if (strongSelf)
1392 [strongSelf showFindBarWithAnimation:NO
1393 selectText:NO
1394 shouldFocus:isFocusedBeforeReshow];
1395 };
1396
1397 BOOL enqueued =
1398 [coordinator animateAlongsideTransition:nil completion:completion];
1399 if (!enqueued) {
1400 completion(nil);
1401 }
1402}
1403
1404- (void)dismissViewControllerAnimated:(BOOL)flag
1405 completion:(void (^)())completion {
1406 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271407 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131408 [super dismissViewControllerAnimated:flag
1409 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271410 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131411 [strongSelf setDismissingModal:NO];
1412 [strongSelf setPresenting:NO];
1413 if (completion)
1414 completion();
1415 [[strongSelf dialogPresenter] tryToPresent];
1416 }];
1417}
1418
1419- (void)presentViewController:(UIViewController*)viewControllerToPresent
1420 animated:(BOOL)flag
1421 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271422 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131423 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1424 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1425 // presented, to show a temporary view of the launch screen and then remove it
1426 // when the controller for the FRE has been presented. This fix should be
1427 // removed when the FRE startup code is rewritten.
1428 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1429 experimental_flags::AlwaysDisplayFirstRun()) &&
1430 !tests_hook::DisableFirstRun();
1431 // These if statements check that |presentViewController| is being called for
1432 // the FRE case.
1433 if (firstRunLaunch &&
1434 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1435 UINavigationController* navController =
1436 base::mac::ObjCCastStrict<UINavigationController>(
1437 viewControllerToPresent);
1438 if ([navController.topViewController
1439 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1440 self.hideStatusBar = YES;
1441
1442 // Load view from Launch Screen and add it to window.
1443 NSBundle* mainBundle = base::mac::FrameworkBundle();
1444 NSArray* topObjects =
1445 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1446 UIViewController* launchScreenController =
1447 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1448 // |launchScreenView| is loaded as an autoreleased object, and is retained
1449 // by the |completion| block below.
1450 UIView* launchScreenView = launchScreenController.view;
1451 launchScreenView.userInteractionEnabled = NO;
1452 launchScreenView.frame = self.view.window.bounds;
1453 [self.view.window addSubview:launchScreenView];
1454
1455 // Replace the completion handler sent to the superclass with one which
1456 // removes |launchScreenView| and resets the status bar. If |completion|
1457 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271458 __weak BrowserViewController* weakSelf = self;
1459 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131460 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271461 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131462 if (completion)
1463 completion();
stkhapuginc9eee7b2017-04-10 15:49:271464 };
sdefresnee65fd872016-12-19 13:38:131465 }
1466 }
1467
1468 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171469 if ([_sideSwipeController inSwipe]) {
1470 [_sideSwipeController resetContentView];
1471 }
sdefresnee65fd872016-12-19 13:38:131472
1473 [super presentViewController:viewControllerToPresent
1474 animated:flag
1475 completion:finalCompletionHandler];
1476}
1477
1478#pragma mark - Notification handling
1479
1480- (void)registerForNotifications {
1481 DCHECK(_model);
1482 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1483 [defaultCenter addObserver:self
1484 selector:@selector(pageLoadStarting:)
1485 name:kTabModelTabWillStartLoadingNotification
1486 object:_model];
1487 [defaultCenter addObserver:self
1488 selector:@selector(pageLoadStarted:)
1489 name:kTabModelTabDidStartLoadingNotification
1490 object:_model];
1491 [defaultCenter addObserver:self
1492 selector:@selector(pageLoadComplete:)
1493 name:kTabModelTabDidFinishLoadingNotification
1494 object:_model];
1495 [defaultCenter addObserver:self
1496 selector:@selector(tabDeselected:)
1497 name:kTabModelTabDeselectedNotification
1498 object:_model];
1499 [defaultCenter addObserver:self
1500 selector:@selector(tabWasAdded:)
1501 name:kTabModelNewTabWillOpenNotification
1502 object:_model];
1503}
1504
1505- (void)pageLoadStarting:(NSNotification*)notify {
1506 Tab* tab = notify.userInfo[kTabModelTabKey];
1507 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511508
1509 // Stop any Find in Page searches and close the find bar when navigating to a
1510 // new page.
1511 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361512
sdefresnee65fd872016-12-19 13:38:131513 if (tab == [_model currentTab]) {
1514 // TODO(pinkerton): Fill in here about hiding the forward button on
1515 // navigation.
1516 }
1517}
1518
1519- (void)pageLoadStarted:(NSNotification*)notify {
1520 Tab* tab = notify.userInfo[kTabModelTabKey];
1521 DCHECK(tab);
1522 if (tab == [_model currentTab]) {
1523 if (![self isTabNativePage:tab]) {
1524 [_toolbarController currentPageLoadStarted];
1525 }
1526 [self updateVoiceSearchBarVisibilityAnimated:NO];
1527 }
1528}
1529
1530- (void)pageLoadComplete:(NSNotification*)notify {
1531 // Update the UI, but only if the current tab.
1532 Tab* tab = notify.userInfo[kTabModelTabKey];
1533 if (tab == [_model currentTab]) {
1534 // There isn't any need to update the toolbar here. When the page finishes,
1535 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1536 }
1537
1538 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1539
1540 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1541}
1542
1543- (void)tabDeselected:(NSNotification*)notify {
1544 DCHECK(notify);
1545 Tab* tab = notify.userInfo[kTabModelTabKey];
1546 DCHECK(tab);
1547 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221548 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131549}
1550
1551- (void)tabWasAdded:(NSNotification*)notify {
1552 Tab* tab = notify.userInfo[kTabModelTabKey];
1553 DCHECK(tab);
1554
1555 // Update map if a native controller was vended before the tab was added.
1556 id<CRWNativeContent> nativeController =
1557 [_nativeControllersForTabIDs objectForKey:kNativeControllerTemporaryKey];
1558 if (nativeController) {
1559 [_nativeControllersForTabIDs
1560 removeObjectForKey:kNativeControllerTemporaryKey];
1561 [_nativeControllersForTabIDs setObject:nativeController forKey:tab.tabId];
1562 }
1563
1564 // When adding new tabs, check what kind of reminder infobar should
1565 // be added to the new tab. Try to add only one of them.
1566 // This check is done when a new tab is added either through the Tools Menu
1567 // "New Tab" or through "New Tab" in Stack View Controller. This method
1568 // is called after a new tab has added and finished initial navigation.
1569 // If this is added earlier, the initial navigation may end up clearing
1570 // the infobar(s) that are just added. See http://crbug/340250 for details.
1571 [[UpgradeCenter sharedInstance] addInfoBarToManager:[tab infoBarManager]
1572 forTabId:[tab tabId]];
1573 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1574 ios_internal::sync::displaySyncErrors(_browserState, tab);
1575 }
1576
1577 // The rest of this function initiates the new tab animation, which is
1578 // phone-specific.
1579 if (IsIPadIdiom())
1580 return;
1581
1582 // Do nothing if browsing is currently suspended. The BVC will set everything
1583 // up correctly when browsing resumes.
1584 if (!self.visible || ![_model webUsageEnabled])
1585 return;
1586
1587 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1588
1589 // Block that starts voice search at the end of new Tab animation if
1590 // necessary.
1591 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1592 if (_startVoiceSearchAfterNewTabAnimation) {
1593 _startVoiceSearchAfterNewTabAnimation = NO;
1594 [self startVoiceSearch];
1595 }
1596 };
1597
kkhorimotoa44349c12017-04-12 23:02:121598 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131599 if (!inBackground) {
1600 UIView* animationParentView = _contentArea;
1601 // Create the new page image, and load with the new tab page snapshot.
1602 CGFloat newPageOffset = 0;
1603 UIImageView* newPage;
1604 if (tab.url == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
1605 !IsIPadIdiom()) {
1606 animationParentView = self.view;
1607 newPage = [self pageFullScreenOpenCloseAnimationView];
1608 } else {
1609 newPage = [self pageOpenCloseAnimationView];
1610 }
1611 newPageOffset = newPage.frame.origin.y;
1612
1613 [tab view].frame = _contentArea.bounds;
1614 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1615 [animationParentView addSubview:newPage];
1616 CGPoint origin = [self lastTapPoint];
1617 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1618 newPage, -newPageOffset,
1619 newPage.frame.size.height - newPage.image.size.height, origin,
1620 _isOffTheRecord, NULL, ^{
1621 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121622 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421623 // Use the model's currentTab here because it is possible that it can
1624 // be reset to a new value before the new Tab animation finished (e.g.
1625 // if another Tab shows a dialog via |dialogPresenter|). However, that
1626 // tab's view hasn't been displayed yet because it was in a new tab
1627 // animation.
1628 Tab* currentTab = [_model currentTab];
1629 if (currentTab) {
1630 [self tabSelected:currentTab];
1631 }
sdefresnee65fd872016-12-19 13:38:131632 startVoiceSearchIfNecessaryBlock();
1633 });
1634 } else {
1635 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1636 // snapshot before adding the views needed for the background animation.
1637 Tab* topTab = [_model currentTab];
1638 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1639 visibleFrameOnly:self.isToolbarOnScreen];
1640 // Add three layers in order on top of the contentArea for the animation:
1641 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271642 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131643 InstallBackgroundInView(background);
1644 [_contentArea addSubview:background];
1645
1646 // 2. A CardView displaying the data from the current tab.
1647 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1648 NSString* title = [topTab title];
1649 if (![title length])
1650 title = [topTab urlDisplayString];
1651 [topCard setTitle:title];
1652 [topCard setFavicon:[topTab favicon]];
1653 [topCard setImage:image];
1654
1655 // 3. A new, blank CardView to represent the new tab being added.
1656 // Launch the new background tab animation.
1657 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1658 topCard, [_contentArea frame], IsPortrait(), ^{
1659 [background removeFromSuperview];
1660 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121661 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131662 // Resnapshot the top card if it has its own toolbar, as the toolbar
1663 // will be captured in the new tab animation, but isn't desired for
1664 // the stack view snapshots.
1665 id nativeController = [self nativeControllerForTab:topTab];
1666 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1667 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1668 startVoiceSearchIfNecessaryBlock();
1669 });
1670 }
1671}
1672
1673#pragma mark - UI Configuration and Layout
1674
1675- (void)updateWithTabModel:(TabModel*)model
1676 browserState:(ios::ChromeBrowserState*)browserState {
1677 DCHECK(model);
1678 DCHECK(browserState);
1679 DCHECK(!_model);
1680 DCHECK(!_browserState);
1681 _browserState = browserState;
1682 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271683 _model = model;
sdefresnee65fd872016-12-19 13:38:131684 [_model addObserver:self];
1685
1686 if (!_isOffTheRecord) {
1687 [DefaultIOSWebViewFactory
1688 registerWebViewFactory:[ChromeWebViewFactory class]];
1689 }
1690 NSUInteger count = [_model count];
1691 for (NSUInteger index = 0; index < count; ++index)
1692 [self installDelegatesForTab:[_model tabAtIndex:index]];
1693
1694 [self registerForNotifications];
1695
gambardbdc07cc2017-02-03 16:43:111696 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
1697 _browserState->GetRequestContext(), web::WebThread::GetBlockingPool());
stkhapuginc9eee7b2017-04-10 15:49:271698 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131699
sdefresnedc432f42017-01-17 14:36:591700 // Register for bookmark changed notification (BookmarkModel may be null
1701 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131702 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591703 if (_bookmarkModel) {
1704 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1705 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1706 }
sdefresnee65fd872016-12-19 13:38:131707}
1708
1709- (void)ensureViewCreated {
1710 ignore_result([self view]);
1711}
1712
1713- (void)browserStateDestroyed {
1714 [self setActive:NO];
1715 // Reset the toolbar opacity in case it was changed for contextual search.
1716 [self updateToolbarControlsAlpha:1.0];
1717 [self updateToolbarBackgroundAlpha:1.0];
1718 [_contextualSearchController close];
stkhapuginc9eee7b2017-04-10 15:49:271719 _contextualSearchController = nil;
sdefresnee65fd872016-12-19 13:38:131720 [_contextualSearchPanel removeFromSuperview];
1721 [_contextualSearchMask removeFromSuperview];
1722 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271723 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131724 [_toolbarController browserStateDestroyed];
1725 [_model browserStateDestroyed];
michaeldobc2f42e2017-01-12 19:04:471726 [_preloadController browserStateDestroyed];
stkhapuginc9eee7b2017-04-10 15:49:271727 _preloadController = nil;
sdefresnee65fd872016-12-19 13:38:131728 // The file remover needs the browser state, so needs to be destroyed now.
stkhapuginc9eee7b2017-04-10 15:49:271729 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131730 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391731 [_dispatcher stopDispatchingToTarget:self];
1732 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131733}
1734
1735- (void)installFakeStatusBar {
1736 if (IsIPadIdiom()) {
1737 CGFloat statusBarHeight = StatusBarHeight();
1738 CGRect statusBarFrame =
1739 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271740 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131741 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1742 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1743 [statusBarView layer].zPosition = 99;
1744 [[self view] addSubview:statusBarView];
1745 }
1746
1747 // Add a white bar on phone so that the status bar on the NTP is white.
1748 if (!IsIPadIdiom()) {
1749 CGFloat statusBarHeight = StatusBarHeight();
1750 CGRect statusBarFrame =
1751 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271752 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131753 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1754 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1755 [self.view insertSubview:statusBarView atIndex:0];
1756 }
1757}
1758
1759// Create the UI elements. May or may not have valid browser state & tab model.
1760- (void)buildToolbarAndTabStrip {
1761 DCHECK([self isViewLoaded]);
1762 DCHECK(!_toolbarModelDelegate);
1763
1764 // Create the preload controller before the toolbar controller.
1765 if (!_preloadController) {
stkhapuginc9eee7b2017-04-10 15:49:271766 _preloadController = [_dependencyFactory newPreloadController];
sdefresnee65fd872016-12-19 13:38:131767 [_preloadController setDelegate:self];
1768 }
1769
1770 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201771 _toolbarModelDelegate.reset(
1772 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131773 _toolbarModelIOS.reset([_dependencyFactory
1774 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
stkhapuginc9eee7b2017-04-10 15:49:271775 _toolbarController = [_dependencyFactory
sdefresnee65fd872016-12-19 13:38:131776 newWebToolbarControllerWithDelegate:self
1777 urlLoader:self
stkhapuginc9eee7b2017-04-10 15:49:271778 preloadProvider:_preloadController];
justincohen75011c32017-04-28 16:31:391779 [_dispatcher startDispatchingToTarget:_toolbarController
1780 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131781 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271782 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131783 _voiceSearchController->SetDelegate(_toolbarController);
1784
1785 // If needed, create the tabstrip.
1786 if (IsIPadIdiom()) {
stkhapuginc9eee7b2017-04-10 15:49:271787 _tabStripController =
1788 [_dependencyFactory newTabStripControllerWithTabModel:_model];
1789 _tabStripController.fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131790 }
1791
1792 // Create infobar container.
1793 if (!_infoBarContainerDelegate) {
1794 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1795 _infoBarContainer.reset(
1796 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1797 }
1798}
1799
1800// Enable functionality that only makes sense if the views are loaded and
1801// both browser state and tab model are valid.
1802- (void)addUIFunctionalityForModelAndBrowserState {
1803 DCHECK(_browserState);
1804 DCHECK(_model);
1805 DCHECK([self isViewLoaded]);
1806
1807 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1808
1809 infobars::InfoBarManager* infoBarManager =
1810 [[_model currentTab] infoBarManager];
1811 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1812
1813 // Create contextual search views and controller.
1814 if ([TouchToSearchPermissionsMediator isTouchToSearchAvailableOnDevice] &&
1815 !_browserState->IsOffTheRecord()) {
stkhapuginf58b10d02017-04-10 13:36:171816 _contextualSearchMask = [[ContextualSearchMaskView alloc] init];
sdefresnee65fd872016-12-19 13:38:131817 [self.view insertSubview:_contextualSearchMask
1818 belowSubview:[_toolbarController view]];
1819 _contextualSearchPanel = [self createPanelView];
1820 [self.view insertSubview:_contextualSearchPanel
1821 aboveSubview:[_toolbarController view]];
stkhapuginc9eee7b2017-04-10 15:49:271822 _contextualSearchController =
1823 [[ContextualSearchController alloc] initWithBrowserState:_browserState
1824 delegate:self];
sdefresnee65fd872016-12-19 13:38:131825 [_contextualSearchController setPanel:_contextualSearchPanel];
1826 [_contextualSearchController setTab:[_model currentTab]];
1827 }
1828
1829 if (experimental_flags::IsPaymentRequestEnabled()) {
stkhapuginc9eee7b2017-04-10 15:49:271830 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131831 initWithBaseViewController:self
stkhapuginc9eee7b2017-04-10 15:49:271832 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131833 [_paymentRequestManager setWebState:[_model currentTab].webState];
1834 }
1835}
1836
1837// Set the frame for the various views. View must be loaded.
1838- (void)setUpViewLayout {
1839 DCHECK([self isViewLoaded]);
1840
1841 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1842
1843 CGFloat minY = [self headerOffset];
1844
1845 // If needed, position the tabstrip.
1846 if (IsIPadIdiom()) {
1847 [self layoutTabStripForWidth:widthOfView];
1848 [[self view] addSubview:[_tabStripController view]];
1849 minY += CGRectGetHeight([[_tabStripController view] frame]);
1850 }
1851
1852 // Position the toolbar next, either at the top of the browser view or
1853 // directly under the tabstrip.
1854 CGRect toolbarFrame = [[_toolbarController view] frame];
1855 toolbarFrame.origin = CGPointMake(0, minY);
1856 toolbarFrame.size.width = widthOfView;
1857 [[_toolbarController view] setFrame:toolbarFrame];
1858
1859 // Place the infobar container above the content area.
1860 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1861 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1862
1863 // Place the toolbar controller above the infobar container.
1864 [[self view] insertSubview:[_toolbarController view]
1865 aboveSubview:infoBarContainerView];
1866 minY += CGRectGetHeight(toolbarFrame);
1867
1868 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1869 // content slightly.
1870 minY -= [ToolbarController toolbarDropShadowHeight];
1871
1872 // Adjust the content area to be under the toolbar, for fullscreen or below
1873 // the toolbar is not fullscreen.
1874 CGRect contentFrame = [_contentArea frame];
1875 CGFloat marginWithHeader = StatusBarHeight();
1876 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1877 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1878 contentFrame.origin.y = overlap;
1879 [_contentArea setFrame:contentFrame];
1880
1881 // Adjust the infobar container to be either at the bottom of the screen
1882 // (iPhone) or on the lower toolbar edge (iPad).
1883 CGRect infoBarFrame = contentFrame;
1884 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1885 infoBarFrame.size.height = 0;
1886 [infoBarContainerView setFrame:infoBarFrame];
1887
1888 // Attach the typing shield to the content area but have it hidden.
1889 [_typingShield setFrame:[_contentArea frame]];
1890 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1891 [_typingShield setHidden:YES];
1892 _typingShield.accessibilityIdentifier = @"Typing Shield";
1893 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1894}
1895
1896- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1897 UIView* tabStripView = [_tabStripController view];
1898 CGRect tabStripFrame = [tabStripView frame];
1899 tabStripFrame.origin = CGPointZero;
1900 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1901 // because the CGPointZero above will break reset the offset, but it's not
1902 // clear what removing that will do.
1903 tabStripFrame.origin.y = [self headerOffset];
1904 tabStripFrame.size.width = maxWidth;
1905 [tabStripView setFrame:tabStripFrame];
1906}
1907
1908- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1909 DCHECK(tab);
1910 // Ensure that self.view is loaded to avoid errors that can otherwise occur
1911 // when accessing |_contentArea| below.
1912 if (!_contentArea)
1913 [self ensureViewCreated];
1914
1915 DCHECK(_contentArea);
kkhorimotoa44349c12017-04-12 23:02:121916 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:131917 // Hide findbar. |updateToolbar| will restore the findbar later.
1918 [self hideFindBarWithAnimation:NO];
1919
1920 // Make new content visible, resizing it first as the orientation may
1921 // have changed from the last time it was displayed.
1922 [[tab view] setFrame:_contentArea.bounds];
1923 [_contentArea displayContentView:[tab view]];
1924 }
1925 [self updateToolbar];
1926
1927 if (newSelection)
1928 [_toolbarController selectedTabChanged];
1929
1930 // Notify the Tab that it was displayed.
1931 [tab wasShown];
1932}
1933
1934- (void)initializeBookmarkInteractionController {
1935 if (_bookmarkInteractionController)
1936 return;
stkhapuginc9eee7b2017-04-10 15:49:271937 _bookmarkInteractionController =
1938 [[BookmarkInteractionController alloc] initWithBrowserState:_browserState
1939 loader:self
1940 parentController:self];
sdefresnee65fd872016-12-19 13:38:131941}
1942
1943// Update the state of back and forward buttons, hiding the forward button if
1944// there is nowhere to go. Assumes the model's current tab is up to date.
1945- (void)updateToolbar {
1946 // If the BVC has been partially torn down for low memory, wait for the
1947 // view rebuild to handle toolbar updates.
1948 if (!(_toolbarModelIOS && _browserState))
1949 return;
1950
1951 Tab* tab = [_model currentTab];
1952 if (![tab navigationManager])
1953 return;
1954 [_toolbarController updateToolbarState];
1955 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1956
1957 if (tab.isPrerenderTab && !_toolbarModelIOS->IsLoading())
1958 [_toolbarController showPrerenderingAnimation];
1959
1960 // Also update the loading state for the tools menu (that is really an
1961 // extension of the toolbar on the iPhone).
1962 if (!IsIPadIdiom())
1963 [[_toolbarController toolsPopupController]
1964 setIsTabLoading:_toolbarModelIOS->IsLoading()];
1965
rohitrao005a6432017-03-16 20:52:421966 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
1967 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:131968 [self showFindBarWithAnimation:NO
1969 selectText:YES
1970 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:361971 }
sdefresnee65fd872016-12-19 13:38:131972
1973 // Hide the toolbar if displaying phone NTP.
1974 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:551975 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:131976 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:551977 if (item) {
1978 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:131979 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
1980 hideToolbar = isNTP && !_isOffTheRecord &&
1981 ![_toolbarController isOmniboxFirstResponder] &&
1982 ![_toolbarController showingOmniboxPopup];
1983 }
1984 [[_toolbarController view] setHidden:hideToolbar];
1985 }
1986}
1987
1988- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:121989 self.dialogPresenter.active =
1990 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:131991}
1992
1993- (void)dismissPopups {
jif7fed8122017-02-08 13:15:251994 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:131995 [self hidePageInfoPopupForView:nil];
1996 [_toolbarController dismissTabHistoryPopup];
sdefresnee65fd872016-12-19 13:38:131997}
1998
1999#pragma mark - Tap handling
2000
2001- (void)setLastTapPoint:(id)sender {
2002 CGPoint center;
2003 UIView* parentView = nil;
2004 if ([sender isKindOfClass:[UIView class]]) {
2005 center = [sender center];
2006 parentView = [sender superview];
2007 }
2008 if ([sender isKindOfClass:[ToolsMenuViewItem class]]) {
2009 parentView = [[sender tableViewCell] superview];
2010 center = [[sender tableViewCell] center];
2011 }
2012
2013 if (parentView) {
2014 _lastTapPoint = [parentView convertPoint:center toView:self.view];
2015 _lastTapTime = CACurrentMediaTime();
2016 }
2017}
2018
2019- (CGPoint)lastTapPoint {
2020 if (CACurrentMediaTime() - _lastTapTime < 1) {
2021 return _lastTapPoint;
2022 }
2023 return CGPointZero;
2024}
2025
2026- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2027 UIView* view = gestureRecognizer.view;
2028 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2029 _lastTapPoint =
2030 [[view superview] convertPoint:viewCoordinate toView:self.view];
2031 _lastTapTime = CACurrentMediaTime();
2032}
2033
2034- (BOOL)addTabIfNoTabWithNormalBrowserState {
2035 if (![_model count]) {
2036 if (!_isOffTheRecord) {
2037 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2038 transition:ui::PAGE_TRANSITION_TYPED];
2039 return YES;
2040 }
2041 }
2042 return NO;
2043}
2044
2045#pragma mark - Tab creation and selection
2046
2047// Called when either a tab finishes loading or when a tab with finished content
2048// is added directly to the model via pre-rendering.
2049- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2050 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2051
2052 // Persist the session on a delay.
2053 [_model saveSessionImmediately:NO];
2054}
2055
2056- (Tab*)addSelectedTabWithURL:(const GURL&)url
2057 postData:(TemplateURLRef::PostContent*)postData
2058 transition:(ui::PageTransition)transition {
2059 return [self addSelectedTabWithURL:url
2060 postData:postData
2061 atIndex:[_model count]
2062 transition:transition];
2063}
2064
2065- (Tab*)addSelectedTabWithURL:(const GURL&)url
2066 transition:(ui::PageTransition)transition {
2067 return [self addSelectedTabWithURL:url
2068 atIndex:[_model count]
2069 transition:transition];
2070}
2071
2072- (Tab*)addSelectedTabWithURL:(const GURL&)url
2073 atIndex:(NSUInteger)position
2074 transition:(ui::PageTransition)transition {
2075 return [self addSelectedTabWithURL:url
2076 postData:NULL
2077 atIndex:position
2078 transition:transition];
2079}
2080
2081- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2082 postData:(TemplateURLRef::PostContent*)postData
2083 atIndex:(NSUInteger)position
2084 transition:(ui::PageTransition)transition {
2085 if (position == NSNotFound)
2086 position = [_model count];
2087 DCHECK(position <= [_model count]);
2088
2089 web::NavigationManager::WebLoadParams params(URL);
2090 params.transition_type = transition;
2091 if (postData) {
2092 // Extract the content type and post params from |postData| and add them
2093 // to the load params.
2094 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2095 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2096 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172097 params.post_data.reset(data);
2098 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132099 }
sdefresnea6395912017-03-01 01:14:352100 Tab* tab = [_model insertTabWithLoadParams:params
2101 opener:nil
2102 openedByDOM:NO
2103 atIndex:position
2104 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132105 return tab;
2106}
2107
olivierrobin889af53f2017-03-01 14:56:322108// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132109- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322110 web::WebState* webState = tab.webState;
2111 if (!webState)
2112 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392113 web::NavigationItem* visibleItem =
2114 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322115 if (!visibleItem)
2116 return NO;
2117 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132118}
2119
2120- (void)expectNewForegroundTab {
2121 _expectingForegroundTab = YES;
2122}
2123
2124- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2125 CGRect viewBounds, remainder;
2126 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2127 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172128 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132129}
2130
2131- (UIImageView*)pageOpenCloseAnimationView {
2132 CGRect frame = [_contentArea bounds];
2133
2134 frame.size.height = frame.size.height - [self headerHeight];
2135 frame.origin.y = [self headerHeight];
2136
stkhapuginf58b10d02017-04-10 13:36:172137 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132138 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2139 pageView.center = center;
2140
2141 pageView.backgroundColor = [UIColor whiteColor];
2142 return pageView;
2143}
2144
2145- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142146 // Unregistration happens when the Tab is removed from the TabModel.
sdefresnee65fd872016-12-19 13:38:132147 tab.dialogDelegate = self;
2148 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132149 tab.passKitDialogProvider = self;
2150 tab.fullScreenControllerDelegate = self;
2151 if (!IsIPadIdiom()) {
2152 tab.overscrollActionsControllerDelegate = self;
2153 }
olivierrobin9ce77b82017-01-12 17:29:192154 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132155 tab.tabSnapshottingDelegate = self;
2156 // Install the proper CRWWebController delegates.
2157 tab.webController.nativeProvider = self;
2158 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512159 // BrowserViewController presents SKStoreKitViewController on behalf of a
2160 // tab.
2161 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2162 if (tabHelper)
2163 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132164 tab.webState->SetDelegate(_webStateDelegate.get());
2165}
2166
sdefresne49cf2862017-03-15 13:46:142167- (void)uninstallDelegatesForTab:(Tab*)tab {
2168 tab.dialogDelegate = nil;
2169 tab.snapshotOverlayProvider = nil;
2170 tab.passKitDialogProvider = nil;
2171 tab.fullScreenControllerDelegate = nil;
2172 if (!IsIPadIdiom()) {
2173 tab.overscrollActionsControllerDelegate = nil;
2174 }
2175 tab.tabHeadersDelegate = nil;
2176 tab.tabSnapshottingDelegate = nil;
2177 tab.webController.nativeProvider = nil;
2178 tab.webController.swipeRecognizerProvider = nil;
2179 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2180 if (tabHelper)
2181 tabHelper->SetLauncher(nil);
2182 tab.webState->SetDelegate(nullptr);
2183}
2184
sdefresnee65fd872016-12-19 13:38:132185// Called when a tab is selected in the model. Make any required view changes.
2186// The notification will not be sent when the tab is already the selected tab.
2187- (void)tabSelected:(Tab*)tab {
2188 DCHECK(tab);
2189
2190 // Ignore changes while the tab stack view is visible (or while suspended).
2191 // The display will be refreshed when this view becomes active again.
2192 if (!self.visible || ![_model webUsageEnabled])
2193 return;
2194
2195 [self displayTab:tab isNewSelection:YES];
2196
kkhorimotoa44349c12017-04-12 23:02:122197 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132198 // Now that the new tab has been displayed, return to normal. Rather than
2199 // keep a reference to the previous tab, just turn off preview mode for all
2200 // tabs (since doing so is a no-op for the tabs that don't have it set).
2201 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272202 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132203 [tab.webController setOverlayPreviewMode:NO];
2204 }
2205 }
2206}
2207
2208#pragma mark - External files
2209
2210- (NSSet*)referencedExternalFiles {
2211 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2212
2213 // TODO(noyau): this is incorrect, the caller should know that the model is
2214 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592215 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132216 return filesReferencedByTabs;
2217
2218 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2219 _bookmarkModel->GetBookmarks(&bookmarks);
2220 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2221 for (const auto& bookmark : bookmarks) {
2222 GURL bookmarkUrl = bookmark.url;
2223 if (UrlIsExternalFileReference(bookmarkUrl)) {
2224 [bookmarkedFiles
2225 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2226 }
2227 }
2228 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2229}
2230
2231- (void)removeExternalFilesImmediately:(BOOL)immediately
2232 completionHandler:(ProceduralBlock)completionHandler {
2233 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2234 DCHECK(!_isOffTheRecord);
2235 _externalFileRemover.reset(new ExternalFileRemover(self));
2236 // Delay the cleanup of the unreferenced files received from other apps
2237 // to not impact startup performance.
2238 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2239 _externalFileRemover->RemoveAfterDelay(
2240 base::TimeDelta::FromSeconds(delay),
stkhapuginf58b10d02017-04-10 13:36:172241 base::BindBlockArc(completionHandler ? completionHandler
2242 : ^{
2243 }));
sdefresnee65fd872016-12-19 13:38:132244}
2245
2246#pragma mark - SnapshotOverlayProvider methods
2247
2248- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2249 NSMutableArray* overlays = [NSMutableArray array];
2250 if (![_model webUsageEnabled]) {
2251 return overlays;
2252 }
2253 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2254 if (voiceSearchView) {
2255 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272256 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132257 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272258 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132259 [overlays addObject:voiceSearchOverlay];
2260 }
2261 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2262 if (infoBarView) {
2263 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272264 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132265 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272266 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132267 [overlays addObject:infoBarOverlay];
2268 }
2269 return overlays;
2270}
2271
2272#pragma mark -
2273
2274- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2275 if (IsIPadIdiom()) {
2276 // Not using overlays on iPad because the content is pushed down by
2277 // infobar and the transition between snapshot and fresh page can
2278 // cause both snapshot and real infobars to appear at the same time.
2279 return nil;
2280 }
2281 Tab* currentTab = [_model currentTab];
2282 if (tab && tab == currentTab) {
2283 infobars::InfoBarManager* infoBarManager = [currentTab infoBarManager];
2284 if (infoBarManager->infobar_count() > 0) {
2285 DCHECK(_infoBarContainer);
2286 return _infoBarContainer->view();
2287 }
2288 }
2289 return nil;
2290}
2291
2292- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272293 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132294 // There is no UI representation for non-current tabs or there is
2295 // no _infoBarContainer instantiated yet.
2296 // Return offset outside of tab.
2297 return CGRectGetMaxY(self.view.frame);
2298 } else if (IsIPadIdiom()) {
2299 // The infobars on iPad are display at the top of a tab.
2300 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2301 } else {
2302 // The infobars on iPhone are displayed at the bottom of a tab.
2303 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2304 return CGRectGetMaxY(visibleFrame) -
2305 CGRectGetHeight(_infoBarContainer->view().frame);
2306 }
2307}
2308
2309- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2310 Tab* currentTab = [_model currentTab];
2311 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2312 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2313 return _voiceSearchBar;
2314 }
2315 return nil;
2316}
2317
2318- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2319 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2320 // There is no UI representation for non-current tabs or there is
2321 // no visible voice search. Return offset outside of tab.
2322 return CGRectGetMaxY(self.view.frame);
2323 } else {
2324 // The voice search bar on iPhone is displayed at the bottom of a tab.
2325 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2326 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2327 }
2328}
2329
2330- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272331 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132332 VoiceSearchProvider* provider =
2333 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2334 if (provider) {
2335 _voiceSearchController =
2336 provider->CreateVoiceSearchController(_browserState);
2337 _voiceSearchController->SetDelegate(_toolbarController);
2338 }
2339 }
2340}
2341
2342- (void)ensureVoiceSearchBarCreated {
2343 if (_voiceSearchBar)
2344 return;
2345
2346 CGFloat width = CGRectGetWidth([[self view] bounds]);
2347 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2348 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272349 _voiceSearchBar = ios::GetChromeBrowserProvider()
2350 ->GetVoiceSearchProvider()
2351 ->BuildVoiceSearchBar(frame);
sdefresnee65fd872016-12-19 13:38:132352 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2353 [_voiceSearchBar setHidden:YES];
2354 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2355 UIViewAutoresizingFlexibleWidth];
2356 [self.view insertSubview:_voiceSearchBar
2357 belowSubview:_infoBarContainer->view()];
2358}
2359
2360- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2361 // Voice search bar exists and is shown/hidden.
2362 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272363 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132364 return;
2365
2366 // Voice search bar doesn't exist and thus is not visible.
2367 if (!_voiceSearchBar && !show)
2368 return;
2369
2370 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272371 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132372 else
stkhapuginc9eee7b2017-04-10 15:49:272373 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132374}
2375
2376- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2377 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2378 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2379 self.shouldShowVoiceSearchBar) {
2380 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2381 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272382 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132383 }
2384 id currentNativeController =
2385 [self nativeControllerForTab:self.tabModel.currentTab];
2386 Protocol* possibleOwnerProtocol =
2387 @protocol(LogoAnimationControllerOwnerOwner);
2388 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2389 [currentNativeController logoAnimationControllerOwner]) {
2390 // If the current native controller is showing a GLIF view (e.g. the NTP
2391 // when there is no doodle), use that GLIFControllerOwner.
2392 return [currentNativeController logoAnimationControllerOwner];
2393 }
2394 return nil;
2395}
2396
2397#pragma mark - PassKitDialogProvider methods
2398
2399- (void)presentPassKitDialog:(NSData*)data {
2400 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272401 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132402 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272403 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132404 if (error || !data) {
2405 if ([_model currentTab]) {
2406 infobars::InfoBarManager* infoBarManager =
2407 [[_model currentTab] infoBarManager];
2408 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2409 // NULL, replace if with DCHECK).
2410 if (infoBarManager)
2411 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2412 }
2413 } else {
2414 PKAddPassesViewController* passKitViewController =
2415 [_dependencyFactory newPassKitViewControllerForPass:pass];
2416 if (passKitViewController) {
2417 [self presentViewController:passKitViewController
2418 animated:YES
2419 completion:^{
2420 }];
2421 }
2422 }
2423}
2424
2425- (UIStatusBarStyle)preferredStatusBarStyle {
2426 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2427 : UIStatusBarStyleDefault;
2428}
2429
2430#pragma mark - CRWWebStateDelegate methods.
2431
eugenebut75a06fa72017-01-09 17:09:552432- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512433 createNewWebStateForURL:(const GURL&)URL
2434 openerURL:(const GURL&)openerURL
2435 initiatedByUser:(BOOL)initiatedByUser {
2436 // Check if requested web state is a popup and block it if necessary.
2437 if (!initiatedByUser) {
2438 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2439 if (helper->ShouldBlockPopup(openerURL)) {
2440 web::NavigationItem* item =
2441 webState->GetNavigationManager()->GetLastCommittedItem();
2442 web::Referrer referrer(openerURL, item->GetReferrer().policy);
2443 helper->HandlePopup(URL, referrer);
2444 return nil;
2445 }
2446 }
2447
2448 // Requested web state should not be blocked from opening.
2449 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2450 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2451
2452 // Tabs open by DOM are always renderer initiated.
2453 web::NavigationManager::WebLoadParams params(GURL{});
2454 params.transition_type = ui::PAGE_TRANSITION_LINK;
2455 params.is_renderer_initiated = true;
2456 Tab* childTab = [[self tabModel]
2457 insertTabWithLoadParams:params
2458 opener:currentTab
2459 openedByDOM:YES
2460 atIndex:TabModelConstants::kTabPositionAutomatically
2461 inBackground:NO];
2462 return childTab.webState;
2463}
2464
eugenebutb46b2122017-03-14 02:43:262465- (void)closeWebState:(web::WebState*)webState {
2466 // Only allow a web page to close itself if it was opened by DOM, or if there
2467 // are no navigation items.
2468 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582469 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262470
2471 if (![self tabModel])
2472 return;
2473
2474 NSUInteger index = [[self tabModel] indexOfTab:tab];
2475 if (index != NSNotFound)
2476 [[self tabModel] closeTabAtIndex:index];
2477}
2478
eugenebut275f5892017-03-09 22:20:512479- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552480 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2481 switch (params.disposition) {
2482 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2483 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2484 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352485 insertTabWithURL:params.url
2486 referrer:params.referrer
2487 transition:params.transition
2488 opener:LegacyTabHelper::GetTabForWebState(webState)
2489 openedByDOM:NO
2490 atIndex:TabModelConstants::kTabPositionAutomatically
2491 inBackground:(params.disposition ==
2492 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552493 return tab.webState;
2494 }
2495 case WindowOpenDisposition::CURRENT_TAB: {
2496 web::NavigationManager::WebLoadParams loadParams(params.url);
2497 loadParams.referrer = params.referrer;
2498 loadParams.transition_type = params.transition;
2499 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2500 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2501 return webState;
2502 }
eugenebutd0984e82017-02-22 23:47:512503 case WindowOpenDisposition::NEW_POPUP: {
2504 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352505 insertTabWithURL:params.url
2506 referrer:params.referrer
2507 transition:params.transition
2508 opener:LegacyTabHelper::GetTabForWebState(webState)
2509 openedByDOM:YES
2510 atIndex:TabModelConstants::kTabPositionAutomatically
2511 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512512 return tab.webState;
2513 }
eugenebut75a06fa72017-01-09 17:09:552514 default:
2515 NOTIMPLEMENTED();
2516 return nullptr;
2517 };
2518}
2519
sdefresnee65fd872016-12-19 13:38:132520- (BOOL)webState:(web::WebState*)webState
2521 handleContextMenu:(const web::ContextMenuParams&)params {
2522 // Prevent context menu from displaying for a tab which is no longer the
2523 // current one.
2524 if (webState != [_model currentTab].webState) {
2525 return NO;
2526 }
2527
2528 // No custom context menu if no valid url is available in |params|.
2529 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
2530 return NO;
2531 }
2532
2533 DCHECK(_browserState);
2534 DCHECK([_model currentTab]);
2535
stkhapuginc9eee7b2017-04-10 15:49:272536 _contextMenuCoordinator =
2537 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2538 params:params];
sdefresnee65fd872016-12-19 13:38:132539
2540 NSString* title = nil;
2541 ProceduralBlock action = nil;
2542
stkhapuginc9eee7b2017-04-10 15:49:272543 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132544 GURL link = params.link_url;
2545 bool isLink = link.is_valid();
2546 GURL imageUrl = params.src_url;
2547 bool isImage = imageUrl.is_valid();
2548
2549 if (isLink) {
2550 if (link.SchemeIs(url::kJavaScriptScheme)) {
2551 // Open
2552 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2553 action = ^{
2554 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2555 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2556 };
2557 [_contextMenuCoordinator addItemWithTitle:title action:action];
2558 }
2559
2560 if (web::UrlHasWebScheme(link)) {
2561 web::Referrer referrer([_model currentTab].url, params.referrer_policy);
2562
sdefresnee65fd872016-12-19 13:38:132563 // Open in New Tab.
2564 title = l10n_util::GetNSStringWithFixup(
2565 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2566 action = ^{
2567 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
2568 [weakSelf webPageOrderedOpen:link
2569 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132570 inBackground:YES
2571 appendTo:kCurrentTab];
2572 };
2573 [_contextMenuCoordinator addItemWithTitle:title action:action];
2574 if (!_isOffTheRecord) {
2575 // Open in Incognito Tab.
2576 title = l10n_util::GetNSStringWithFixup(
2577 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2578 action = ^{
2579 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2580 [weakSelf webPageOrderedOpen:link
2581 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132582 inIncognito:YES
2583 inBackground:NO
2584 appendTo:kCurrentTab];
2585 };
2586 [_contextMenuCoordinator addItemWithTitle:title action:action];
2587 }
olivierrobin51d4cf42017-01-17 13:32:352588 }
gambard65d69152017-03-23 17:44:222589 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352590 NSString* innerText = params.link_text;
2591 if ([innerText length] > 0) {
2592 // Add to reading list.
2593 title = l10n_util::GetNSStringWithFixup(
2594 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2595 action = ^{
2596 Record(ACTION_READ_LATER, isImage, isLink);
2597 [weakSelf addToReadingListURL:link title:innerText];
2598 };
2599 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532600 }
sdefresnee65fd872016-12-19 13:38:132601 }
2602 // Copy Link.
2603 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2604 action = ^{
2605 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282606 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132607 };
2608 [_contextMenuCoordinator addItemWithTitle:title action:action];
2609 }
2610 if (isImage) {
2611 web::Referrer referrer([_model currentTab].url, params.referrer_policy);
2612 // Save Image.
gambard98b4ddf2017-04-18 07:14:052613 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132614 action = ^{
2615 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2616 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2617 };
2618 [_contextMenuCoordinator addItemWithTitle:title action:action];
2619 // Open Image.
2620 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2621 action = ^{
2622 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2623 [weakSelf loadURL:imageUrl
2624 referrer:referrer
2625 transition:ui::PAGE_TRANSITION_LINK
2626 rendererInitiated:YES];
2627 };
2628 [_contextMenuCoordinator addItemWithTitle:title action:action];
2629 // Open Image In New Tab.
2630 title = l10n_util::GetNSStringWithFixup(
2631 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2632 action = ^{
2633 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2634 [weakSelf webPageOrderedOpen:imageUrl
2635 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132636 inBackground:true
2637 appendTo:kCurrentTab];
2638 };
2639 [_contextMenuCoordinator addItemWithTitle:title action:action];
2640
2641 TemplateURLService* service =
2642 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102643 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132644 if (defaultURL && !defaultURL->image_url().empty() &&
2645 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2646 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2647 defaultURL->short_name());
2648 action = ^{
2649 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2650 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2651 };
2652 [_contextMenuCoordinator addItemWithTitle:title action:action];
2653 }
2654 }
2655
2656 [_contextMenuCoordinator start];
2657 return YES;
2658}
2659
eugenebutb739bdc2017-01-25 06:32:482660- (void)webState:(web::WebState*)webState
2661 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2662 // Display the action sheet with the arrow pointing at the top center of the
2663 // web contents.
sdefresne0452a9d2017-02-09 15:33:282664 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482665 UIView* view = webState->GetView();
2666 CGPoint dialogLocation =
2667 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282668 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032669 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172670 helper->PresentDialog(dialogLocation,
2671 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052672 handler(shouldContinue);
2673 }));
eugenebutb739bdc2017-01-25 06:32:482674}
2675
sdefresnee65fd872016-12-19 13:38:132676- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2677 (web::WebState*)webState {
2678 return _javaScriptDialogPresenter.get();
2679}
2680
eugenebut63232102017-01-19 16:19:402681- (void)webState:(web::WebState*)webState
2682 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2683 proposedCredential:(NSURLCredential*)proposedCredential
2684 completionHandler:(void (^)(NSString* username,
2685 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422686 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2687 if ([tab isPrerenderTab]) {
2688 [tab discardPrerender];
2689 if (handler) {
2690 handler(nil, nil);
2691 }
2692 return;
2693 }
2694
eugenebut63232102017-01-19 16:19:402695 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2696 proposedCredential:proposedCredential
2697 webState:webState
2698 completionHandler:handler];
2699}
2700
sdefresnee65fd872016-12-19 13:38:132701#pragma mark - FullScreenControllerDelegate methods
2702
2703- (CGFloat)headerOffset {
2704 if (IsIPadIdiom())
2705 return StatusBarHeight();
2706 return 0.0;
2707}
2708
stkhapugin952ecef2017-04-11 12:11:452709- (NSArray<HeaderDefinition*>*)headerViews {
2710 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132711 if (![self isViewLoaded])
2712 return results;
2713
2714 if (!IsIPadIdiom()) {
2715 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452716 [results addObject:[HeaderDefinition
2717 definitionWithView:[_toolbarController view]
2718 headerBehaviour:Hideable
2719 heightAdjustment:[ToolbarController
2720 toolbarDropShadowHeight]
2721 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132722 }
2723 } else {
2724 if ([_tabStripController view]) {
stkhapugin952ecef2017-04-11 12:11:452725 [results addObject:[HeaderDefinition
2726 definitionWithView:[_tabStripController view]
2727 headerBehaviour:Hideable
2728 heightAdjustment:0.0
2729 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132730 }
2731 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452732 [results addObject:[HeaderDefinition
2733 definitionWithView:[_toolbarController view]
2734 headerBehaviour:Hideable
2735 heightAdjustment:[ToolbarController
2736 toolbarDropShadowHeight]
2737 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132738 }
2739 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452740 [results addObject:[HeaderDefinition
2741 definitionWithView:[_findBarController view]
2742 headerBehaviour:Overlap
2743 heightAdjustment:0.0
2744 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:132745 }
2746 }
stkhapugin952ecef2017-04-11 12:11:452747 return [results copy];
sdefresnee65fd872016-12-19 13:38:132748}
2749
2750- (UIView*)footerView {
2751 return _voiceSearchBar;
2752}
2753
2754- (CGFloat)headerHeight {
2755 return [self headerHeightForTab:[_model currentTab]];
2756}
2757
2758- (CGFloat)headerHeightForTab:(Tab*)tab {
2759 id nativeController = [self nativeControllerForTab:tab];
2760 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2761 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2762 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2763 // On iPhone, don't add any header height for ToolbarOwner native
2764 // controllers when they're displaying their own toolbar.
2765 return 0;
2766 }
2767
stkhapugin952ecef2017-04-11 12:11:452768 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132769
2770 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452771 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:132772 if (header.view && header.behaviour == Hideable) {
2773 height += CGRectGetHeight([header.view frame]) -
2774 header.heightAdjustement - header.inset;
2775 }
2776 }
2777
2778 return height - StatusBarHeight();
2779}
2780
2781- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:192782 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:132783}
2784
2785- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:452786 NSArray<HeaderDefinition*>* headers = [self headerViews];
2787 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:132788 return 0.0;
2789
2790 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
2791 // API documentation.
2792 if ([[[self tabModel] currentTab] isPrerenderTab])
2793 return [self headerHeight];
2794
2795 UIView* topHeader = headers[0].view;
2796 return -(topHeader.frame.origin.y - [self headerOffset]);
2797}
2798
2799- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
2800 UIView* footer = [self footerView];
2801 CGFloat headerHeight = [self headerHeight];
2802 if (!footer || headerHeight == 0)
2803 return 0.0;
2804
2805 CGFloat footerHeight = CGRectGetHeight(footer.frame);
2806 CGFloat offset = headerOffset * footerHeight / headerHeight;
2807 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
2808}
2809
2810- (void)fullScreenController:(FullScreenController*)controller
2811 headerAnimationCompleted:(BOOL)completed
2812 offset:(CGFloat)offset {
2813 if (completed)
justincohen04c27772016-12-21 20:16:592814 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:132815}
2816
stkhapugin952ecef2017-04-11 12:11:452817- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:132818 atOffset:(CGFloat)headerOffset {
2819 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452820 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:132821 CGRect frame = [header.view frame];
2822 frame.origin.y = height - headerOffset - header.inset;
2823 [header.view setFrame:frame];
2824 if (header.behaviour != Overlap)
2825 height += CGRectGetHeight(frame);
2826 }
2827}
2828
2829- (void)fullScreenController:(FullScreenController*)fullScreenController
2830 drawHeaderViewFromOffset:(CGFloat)headerOffset
2831 animate:(BOOL)animate {
2832 if ([_sideSwipeController inSwipe])
2833 return;
2834
2835 CGRect footerFrame = CGRectZero;
2836 UIView* footer = nil;
2837 // Only animate the voice search bar if the tab is a voice search results tab.
2838 if ([_model currentTab].isVoiceSearchResultsTab) {
2839 footer = [self footerView];
2840 footerFrame = footer.frame;
2841 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2842 }
2843
stkhapugin952ecef2017-04-11 12:11:452844 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132845 void (^block)(void) = ^{
2846 [self setFramesForHeaders:headers atOffset:headerOffset];
2847 footer.frame = footerFrame;
2848 };
2849 void (^completion)(BOOL) = ^(BOOL finished) {
2850 [self fullScreenController:fullScreenController
2851 headerAnimationCompleted:finished
2852 offset:headerOffset];
2853 };
2854 if (animate) {
2855 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2856 delay:0.0
2857 options:UIViewAnimationOptionBeginFromCurrentState
2858 animations:block
2859 completion:completion];
2860 } else {
2861 block();
2862 completion(YES);
2863 }
2864}
2865
2866- (void)fullScreenController:(FullScreenController*)fullScreenController
2867 drawHeaderViewFromOffset:(CGFloat)headerOffset
2868 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
2869 changeTopContentPadding:(BOOL)changeTopContentPadding
2870 scrollingToOffset:(CGFloat)contentOffset {
2871 DCHECK(webViewProxy);
2872 if ([_sideSwipeController inSwipe])
2873 return;
2874
2875 CGRect footerFrame;
2876 UIView* footer = nil;
2877 // Only animate the voice search bar if the tab is a voice search results tab.
2878 if ([_model currentTab].isVoiceSearchResultsTab) {
2879 footer = [self footerView];
2880 footerFrame = footer.frame;
2881 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2882 }
2883
stkhapugin952ecef2017-04-11 12:11:452884 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132885 void (^block)(void) = ^{
2886 [self setFramesForHeaders:headers atOffset:headerOffset];
2887 footer.frame = footerFrame;
2888 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
2889 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
2890 if (changeTopContentPadding)
2891 webViewProxy.topContentPadding = contentOffset;
2892 };
2893 void (^completion)(BOOL) = ^(BOOL finished) {
2894 [self fullScreenController:fullScreenController
2895 headerAnimationCompleted:finished
2896 offset:headerOffset];
2897 };
2898
2899 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2900 delay:0.0
2901 options:UIViewAnimationOptionBeginFromCurrentState
2902 animations:block
2903 completion:completion];
2904}
2905
2906#pragma mark - VoiceSearchBarOwner
2907
2908- (id<VoiceSearchBar>)voiceSearchBar {
2909 return _voiceSearchBar;
2910}
2911
2912#pragma mark - Install OverScrollActionController method.
2913- (void)setOverScrollActionControllerToStaticNativeContent:
2914 (StaticHtmlNativeContent*)nativeContent {
2915 if (!IsIPadIdiom() && !FirstRun::IsChromeFirstRun()) {
2916 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:172917 [[OverscrollActionsController alloc]
2918 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:132919 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:052920 OverscrollStyle style = _isOffTheRecord
2921 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2922 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:132923 controller.style = style;
2924 nativeContent.overscrollActionsController = controller;
2925 }
2926}
2927
2928#pragma mark - OverscrollActionsControllerDelegate methods.
2929
2930- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:052931 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:132932 switch (action) {
rohitrao922b7111c2017-01-03 14:31:052933 case OverscrollAction::NEW_TAB:
sdefresnee65fd872016-12-19 13:38:132934 [self newTab:nil];
2935 break;
rohitrao922b7111c2017-01-03 14:31:052936 case OverscrollAction::CLOSE_TAB:
sdefresnee65fd872016-12-19 13:38:132937 [self closeCurrentTab];
2938 break;
liaoyuke563dc4a2017-03-17 18:36:292939 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:132940 if ([[[_model currentTab] webController] loadPhase] ==
2941 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:232942 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:132943 }
liaoyuke563dc4a2017-03-17 18:36:292944
2945 web::WebState* webState = [_model currentTab].webState;
2946 if (webState)
2947 // |check_for_repost| is true because the reload is explicitly initiated
2948 // by the user.
2949 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
2950 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:132951 break;
liaoyuke563dc4a2017-03-17 18:36:292952 }
rohitrao922b7111c2017-01-03 14:31:052953 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:132954 NOTREACHED();
2955 break;
2956 }
2957}
2958
2959- (BOOL)shouldAllowOverscrollActions {
2960 return YES;
2961}
2962
2963- (UIView*)headerView {
2964 return [_toolbarController view];
2965}
2966
2967- (UIView*)toolbarSnapshotView {
2968 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
2969}
2970
2971- (CGFloat)overscrollActionsControllerHeaderInset:
2972 (OverscrollActionsController*)controller {
2973 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
2974 return [self headerHeight];
2975 else
2976 return 0;
2977}
2978
2979- (CGFloat)overscrollHeaderHeight {
2980 return [self headerHeight] + StatusBarHeight();
2981}
2982
2983#pragma mark - TabSnapshottingDelegate methods.
2984
2985- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
2986 CGRect pageContentArea = _contentArea.bounds;
2987 if ([_model webUsageEnabled])
2988 pageContentArea = tab.view.bounds;
2989 CGFloat headerHeight = [self headerHeightForTab:tab];
2990 id nativeController = [self nativeControllerForTab:tab];
2991 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
2992 headerHeight += [nativeController toolbarHeight];
2993 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
2994 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
2995}
2996
2997#pragma mark - NewTabPageObserver methods.
2998
2999- (void)selectedPanelDidChange {
3000 [self updateToolbar];
3001}
3002
3003#pragma mark - CRWNativeContentProvider methods
3004
3005- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3006 withError:(NSError*)error
3007 isPost:(BOOL)isPost {
3008 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173009 [[ErrorPageContent alloc] initWithLoader:self
3010 browserState:self.browserState
3011 url:url
3012 error:error
3013 isPost:isPost
3014 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133015 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3016 return errorPageContent;
3017}
3018
3019- (BOOL)hasControllerForURL:(const GURL&)url {
3020 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453021 if (host == kChromeUIOfflineHost) {
3022 // Only allow offline URL that are fully specified.
3023 return reading_list::IsOfflineURLValid(
3024 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3025 }
sdefresnee65fd872016-12-19 13:38:133026
3027 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost ||
olivierrobin5c861c22017-04-07 15:56:453028 host == kChromeUITermsHost;
sdefresnee65fd872016-12-19 13:38:133029}
3030
olivierrobind43eecb2017-01-27 20:35:263031- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3032 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133033 DCHECK(url.SchemeIs(kChromeUIScheme));
3034
3035 id<CRWNativeContent> nativeController = nil;
3036 std::string url_host = url.host();
3037 if (url_host == kChromeUINewTabHost || url_host == kChromeUIBookmarksHost) {
3038 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173039 [[NewTabPageController alloc] initWithUrl:url
3040 loader:self
3041 focuser:_toolbarController
3042 ntpObserver:self
3043 browserState:_browserState
3044 colorCache:_dominantColorCache
3045 webToolbarDelegate:self
justincohenbc913632017-04-18 14:41:453046 tabModel:_model
justincohen75011c32017-04-28 16:31:393047 parentViewController:self
3048 dispatcher:_dispatcher];
sdefresnee65fd872016-12-19 13:38:133049 pageController.swipeRecognizerProvider = self.sideSwipeController;
3050
3051 // Panel is always NTP for iPhone.
3052 NewTabPage::PanelIdentifier panelType = NewTabPage::kMostVisitedPanel;
3053
3054 if (IsIPadIdiom()) {
3055 // New Tab Page can have multiple panels. Each panel is addressable
3056 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3057 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3058 // the Bookmark Manager, etc.
3059 // The utility functions NewTabPage::IdentifierFromFragment() and
3060 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3061 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3062 // without changing the URL since the URL may be chrome://bookmarks/#123.
3063 // If the URL is chrome://newtab/, pre-select the panel based on the
3064 // #fragment.
3065 panelType = url_host == kChromeUIBookmarksHost
3066 ? NewTabPage::kBookmarksPanel
3067 : NewTabPage::IdentifierFromFragment(url.ref());
3068 }
3069 [pageController selectPanel:panelType];
3070 nativeController = pageController;
3071 } else if (url_host == kChromeUITermsHost) {
3072 const std::string& filename = GetTermsOfServicePath();
3073
3074 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173075 [[StaticHtmlNativeContent alloc]
sdefresnee65fd872016-12-19 13:38:133076 initWithResourcePathResource:base::SysUTF8ToNSString(filename)
3077 loader:self
3078 browserState:_browserState
stkhapuginf58b10d02017-04-10 13:36:173079 url:GURL(kChromeUITermsURL)];
sdefresnee65fd872016-12-19 13:38:133080 [self setOverScrollActionControllerToStaticNativeContent:
3081 staticNativeController];
3082 nativeController = staticNativeController;
olivierrobin5c861c22017-04-07 15:56:453083 } else if (url_host == kChromeUIOfflineHost &&
3084 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133085 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173086 [[OfflinePageNativeContent alloc] initWithLoader:self
3087 browserState:_browserState
3088 webState:webState
3089 URL:url];
sdefresnee65fd872016-12-19 13:38:133090 [self setOverScrollActionControllerToStaticNativeContent:
3091 staticNativeController];
3092 nativeController = staticNativeController;
3093 } else if (url_host == kChromeUIExternalFileHost) {
3094 // Return an instance of the |ExternalFileController| only if the file is
3095 // still in the sandbox.
3096 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3097 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173098 nativeController =
3099 [[ExternalFileController alloc] initWithURL:url
3100 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133101 }
3102 } else {
3103 DCHECK(![self hasControllerForURL:url]);
3104 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173105 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133106 if (url_host == kChromeUIHistoryFrameHost) {
3107 base::mac::ObjCCastStrict<PageNotAvailableController>(nativeController)
3108 .descriptionText = l10n_util::GetNSStringFWithFixup(
3109 IDS_IOS_HISTORY_URL_NOT_AVAILABLE,
3110 base::UTF8ToUTF16(kChromeUIHistoryURL),
3111 l10n_util::GetStringUTF16(IDS_HISTORY_SHOW_HISTORY));
3112 }
3113 }
3114 // If a native controller is vended before its tab is added to the tab model,
3115 // use the temporary key and add it under the new tab's tabId in the
3116 // TabModelObserver callback. This happens:
3117 // - when there is no current tab (occurs when vending the NTP controller for
3118 // the first tab that is opened),
3119 // - when the current tab's url doesn't match |url| (occurs when a native
3120 // controller is opened in a new tab)
3121 // - when the current tab's url matches |url| and there is already a native
3122 // controller of the appropriate type vended to it (occurs when a native
3123 // controller is opened in a new tab from a tab with a matching URL, e.g.
3124 // opening an NTP when an NTP is already displayed in the current tab).
3125 // For normal page loads, history navigations, tab restorations, and crash
3126 // recoveries, the tab will already exist in the tab model and the tabId can
3127 // be used as the native controller key.
3128 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3129 // that native controllers vended here always correspond to the current tab.
3130 Tab* currentTab = [_model currentTab];
3131 NSString* nativeControllerKey = currentTab.tabId;
3132 if (!currentTab || currentTab.url != url ||
3133 [[_nativeControllersForTabIDs objectForKey:nativeControllerKey]
3134 isKindOfClass:[nativeController class]]) {
3135 nativeControllerKey = kNativeControllerTemporaryKey;
3136 }
3137 DCHECK(nativeControllerKey);
3138 [_nativeControllersForTabIDs setObject:nativeController
3139 forKey:nativeControllerKey];
3140 return nativeController;
3141}
3142
3143- (id)nativeControllerForTab:(Tab*)tab {
3144 id nativeController = [_nativeControllersForTabIDs objectForKey:tab.tabId];
3145 if (!nativeController) {
3146 // If there is no controller, check for a native controller stored under
3147 // the temporary key.
3148 nativeController = [_nativeControllersForTabIDs
3149 objectForKey:kNativeControllerTemporaryKey];
3150 }
3151 return nativeController;
3152}
3153
3154#pragma mark - DialogPresenterDelegate methods
3155
3156- (void)dialogPresenter:(DialogPresenter*)presenter
3157 willShowDialogForWebState:(web::WebState*)webState {
3158 for (Tab* iteratedTab in self.tabModel) {
3159 if ([iteratedTab webState] == webState) {
3160 self.tabModel.currentTab = iteratedTab;
3161 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3162 break;
3163 }
3164 }
3165}
3166
3167#pragma mark - Context menu methods
3168
3169- (void)searchByImageAtURL:(const GURL&)url
3170 referrer:(const web::Referrer)referrer {
3171 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273172 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113173 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173174 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3175 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113176 DCHECK(data);
3177 dispatch_async(dispatch_get_main_queue(), ^{
3178 [weakSelf searchByImageData:data atURL:image_source_url];
3179 });
3180 };
3181 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133182 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3183 web::PolicyForNavigation(url, referrer));
3184}
3185
3186- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3187 NSData* imageData = data;
3188 UIImage* image = [UIImage imageWithData:imageData];
3189 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3190 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3191 // kSearchByImageMaxImageHeight).
3192 if (image &&
3193 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3194 (image.size.width > kSearchByImageMaxImageWidth ||
3195 image.size.height > kSearchByImageMaxImageHeight)) {
3196 CGSize newImageSize =
3197 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3198 image = [image gtm_imageByResizingToSize:newImageSize
3199 preserveAspectRatio:YES
3200 trimToFit:NO];
3201 imageData = UIImageJPEGRepresentation(image, 1.0);
3202 }
3203
3204 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3205 std::string byteString(bytes, [imageData length]);
3206
3207 TemplateURLService* templateUrlService =
3208 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103209 const TemplateURL* defaultURL =
3210 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133211 DCHECK(!defaultURL->image_url().empty());
3212 DCHECK(defaultURL->image_url_ref().IsValid(
3213 templateUrlService->search_terms_data()));
3214 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3215 search_args.image_url = imageURL;
3216 search_args.image_thumbnail_content = byteString;
3217
3218 // Generate the URL and populate |post_content| with the content type and
3219 // HTTP body for the request.
3220 TemplateURLRef::PostContent post_content;
3221 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3222 search_args, templateUrlService->search_terms_data(), &post_content));
3223 [self addSelectedTabWithURL:result
3224 postData:&post_content
3225 transition:ui::PAGE_TRANSITION_TYPED];
3226}
3227
3228- (void)saveImageAtURL:(const GURL&)url
3229 referrer:(const web::Referrer&)referrer {
3230 DCHECK(url.is_valid());
3231
gambard9efce7a2017-02-09 18:53:173232 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3233 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113234 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133235
gambard9efce7a2017-02-09 18:53:173236 base::FilePath::StringType extension;
3237
3238 bool extensionSuccess =
3239 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3240 if (!extensionSuccess || extension.length() == 0) {
3241 extension = "png";
3242 }
3243
3244 NSString* fileExtension =
3245 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3246 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113247 };
3248 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133249 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3250 web::PolicyForNavigation(url, referrer));
3251}
3252
gambard9efce7a2017-02-09 18:53:173253- (void)managePermissionAndSaveImage:(NSData*)data
3254 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133255 switch ([PHPhotoLibrary authorizationStatus]) {
3256 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173257 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133258 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3259 // Call -saveImage again to check if chrome needs to display an error or
3260 // saves the image.
3261 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173262 [self managePermissionAndSaveImage:data
3263 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133264 }];
3265 break;
stkhapuginf58b10d02017-04-10 13:36:173266 }
sdefresnee65fd872016-12-19 13:38:133267
3268 // The application doesn't have permission to access photo and the user
3269 // cannot grant it.
3270 case PHAuthorizationStatusRestricted:
3271 [self displayPrivacyErrorAlertOnMainQueue:
3272 l10n_util::GetNSString(
3273 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3274 break;
3275
3276 // The application doesn't have permission to access photo and the user
3277 // can grant it.
3278 case PHAuthorizationStatusDenied:
3279 [self displayImageErrorAlertWithSettingsOnMainQueue];
3280 break;
3281
3282 // The application has permission to access the photos.
3283 default: {
gambard9efce7a2017-02-09 18:53:173284 web::WebThread::PostTask(
stkhapuginf58b10d02017-04-10 13:36:173285 web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
gambard9efce7a2017-02-09 18:53:173286 [self saveImage:data withFileExtension:fileExtension];
3287 }));
sdefresnee65fd872016-12-19 13:38:133288 break;
3289 }
3290 }
3291}
3292
gambard9efce7a2017-02-09 18:53:173293- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension {
gambard16cfe1f2016-12-21 13:12:093294 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
gambard9efce7a2017-02-09 18:53:173295 stringByAppendingString:fileExtension];
sdefresnee65fd872016-12-19 13:38:133296 NSURL* fileURL =
3297 [NSURL fileURLWithPath:[NSTemporaryDirectory()
3298 stringByAppendingPathComponent:fileName]];
3299 NSError* error = nil;
3300 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3301
3302 // Error while writing the image to disk.
3303 if (error) {
3304 NSString* errorMessage = [NSString
3305 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3306 [error domain], [error code]];
3307 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3308 return;
3309 }
3310
3311 // Save the image to photos.
3312 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3313 [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
3314 }
3315 completionHandler:^(BOOL success, NSError* error) {
3316 // Callback for the image saving.
3317 [self finishSavingImageWithError:error];
3318
3319 // Cleanup the temporary file.
3320 web::WebThread::PostTask(
stkhapuginf58b10d02017-04-10 13:36:173321 web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
sdefresnee65fd872016-12-19 13:38:133322 NSError* error = nil;
3323 [[NSFileManager defaultManager] removeItemAtURL:fileURL
3324 error:&error];
3325 }));
3326 }];
3327}
3328
3329- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3330 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3331 BOOL canGoToSetting =
3332 [[UIApplication sharedApplication] canOpenURL:settingURL];
3333 if (canGoToSetting) {
3334 dispatch_async(dispatch_get_main_queue(), ^{
3335 [self displayImageErrorAlertWithSettings:settingURL];
3336 });
3337 } else {
3338 [self displayPrivacyErrorAlertOnMainQueue:
3339 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3340 }
3341}
3342
3343- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3344 // Dismiss current alert.
3345 [_alertCoordinator stop];
3346
3347 NSString* title =
3348 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3349 NSString* message = l10n_util::GetNSString(
3350 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3351
stkhapuginc9eee7b2017-04-10 15:49:273352 _alertCoordinator =
3353 [[AlertCoordinator alloc] initWithBaseViewController:self
3354 title:title
3355 message:message];
sdefresnee65fd872016-12-19 13:38:133356
3357 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3358 action:nil
3359 style:UIAlertActionStyleCancel];
3360
3361 [_alertCoordinator
3362 addItemWithTitle:l10n_util::GetNSString(
3363 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3364 action:^{
3365 OpenUrlWithCompletionHandler(settingURL, nil);
3366 }
3367 style:UIAlertActionStyleDefault];
3368
3369 [_alertCoordinator start];
3370}
3371
3372- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3373 dispatch_async(dispatch_get_main_queue(), ^{
3374 NSString* title =
3375 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3376 [self showErrorAlertWithStringTitle:title message:errorContent];
3377 });
3378}
3379
3380// This callback is triggered when the image is effectively saved onto the photo
3381// album, or if the save failed for some reason.
3382- (void)finishSavingImageWithError:(NSError*)error {
3383 // Was there an error?
3384 if (error) {
3385 // Saving photo failed even though user has granted access to Photos.
3386 // Display the error information from the NSError object for user.
3387 NSString* errorMessage = [NSString
3388 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3389 [error domain], [error code]];
3390 // This code may be execute outside of the main thread. Make sure to display
3391 // the error on the main thread.
3392 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3393 } else {
3394 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3395 // the photo application. The current behaviour is to create the photo there
3396 // but not providing any link to it is suboptimal. That's what Safari is
3397 // doing, and what the PM want, but it doesn't make it right.
3398 }
3399}
3400
sdefresnee65fd872016-12-19 13:38:133401#pragma mark - Showing popups
3402
3403- (void)showToolsMenuPopup {
3404 DCHECK(_browserState);
3405 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133406
3407 // Dismiss the omnibox (if open).
3408 [_toolbarController cancelOmniboxEdit];
3409 // Dismiss the soft keyboard (if open).
3410 [[_model currentTab].webController dismissKeyboard];
3411 // Dismiss Find in Page focus.
3412 [self updateFindBar:NO shouldFocus:NO];
3413
stkhapuginc9eee7b2017-04-10 15:49:273414 ToolsMenuConfiguration* configuration =
3415 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
sdefresnee65fd872016-12-19 13:38:133416 if ([_model count] == 0)
liaoyukeea9f3ee62017-03-07 22:05:393417 [configuration setNoOpenedTabs:YES];
3418
sdefresnee65fd872016-12-19 13:38:133419 if (_isOffTheRecord)
liaoyukeea9f3ee62017-03-07 22:05:393420 [configuration setInIncognito:YES];
3421
gambard65d69152017-03-23 17:44:223422 if (!_readingListMenuNotifier) {
stkhapuginc9eee7b2017-04-10 15:49:273423 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
gambard65d69152017-03-23 17:44:223424 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
stkhapuginc9eee7b2017-04-10 15:49:273425 _browserState)];
sdefresnee65fd872016-12-19 13:38:133426 }
gambard65d69152017-03-23 17:44:223427 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
sdefresnee65fd872016-12-19 13:38:133428
liaoyukeea9f3ee62017-03-07 22:05:393429 [configuration setUserAgentType:self.userAgentType];
3430
3431 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
3432
sdefresnee65fd872016-12-19 13:38:133433 ToolsPopupController* toolsPopupController =
3434 [_toolbarController toolsPopupController];
3435 if ([_model currentTab]) {
3436 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
3437 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
3438 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
3439 [toolsPopupController setCanUseReaderMode:self.canUseReaderMode];
sdefresnee65fd872016-12-19 13:38:133440 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
3441
3442 if (!IsIPadIdiom())
3443 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
3444 }
3445}
3446
3447- (void)showPageInfoPopupForView:(UIView*)sourceView {
3448 Tab* tab = [_model currentTab];
3449 DCHECK([tab navigationManager]);
3450 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3451
3452 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3453 // be trigerred by a button enabled when a current item matches some
3454 // conditions. However a crash was seen were navItem was NULL hence this
3455 // test after a DCHECK.
3456 DCHECK(navItem);
3457 if (!navItem)
3458 return;
3459
olivierrobin013ba672017-03-01 21:16:243460 // Don't show if the page is native except for offline pages (to show the
3461 // offline page info).
3462 if ([self isTabNativePage:tab] &&
3463 !reading_list::IsOfflineURL(navItem->GetURL())) {
sdefresnee65fd872016-12-19 13:38:133464 return;
olivierrobin013ba672017-03-01 21:16:243465 }
sdefresnee65fd872016-12-19 13:38:133466
justincohenb1a73cf2017-02-06 20:25:433467 // Don't show the bubble twice (this can happen when tapping very quickly in
3468 // accessibility mode).
3469 if (_pageInfoController)
3470 return;
3471
sdefresnee65fd872016-12-19 13:38:133472 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3473
3474 // Dismiss the omnibox (if open).
3475 [_toolbarController cancelOmniboxEdit];
3476
3477 [[NSNotificationCenter defaultCenter]
3478 postNotificationName:ios_internal::kPageInfoWillShowNotification
3479 object:nil];
3480
3481 // TODO(rohitrao): Get rid of PageInfoModel completely.
3482 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3483 PageInfoModel* pageInfoModel = new PageInfoModel(
3484 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3485
3486 UIView* view = [self view];
stkhapuginc9eee7b2017-04-10 15:49:273487 _pageInfoController = [[PageInfoViewController alloc]
sdefresnee65fd872016-12-19 13:38:133488 initWithModel:pageInfoModel
3489 bridge:bridge
3490 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
stkhapuginc9eee7b2017-04-10 15:49:273491 parentView:view];
3492 bridge->set_controller(_pageInfoController);
sdefresnee65fd872016-12-19 13:38:133493}
3494
3495- (void)hidePageInfoPopupForView:(UIView*)sourceView {
3496 [_pageInfoController dismiss];
stkhapuginc9eee7b2017-04-10 15:49:273497 _pageInfoController = nil;
sdefresnee65fd872016-12-19 13:38:133498}
3499
3500- (void)showSecurityHelpPage {
3501 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3502 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133503 inBackground:NO
3504 appendTo:kCurrentTab];
3505 [self hidePageInfoPopupForView:nil];
3506}
3507
3508- (void)showTabHistoryPopupForBackwardHistory {
3509 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133510
3511 // Dismiss the omnibox (if open).
3512 [_toolbarController cancelOmniboxEdit];
3513 // Dismiss the soft keyboard (if open).
3514 Tab* tab = [_model currentTab];
3515 [tab.webController dismissKeyboard];
3516
kkhorimoto1c12cbf2017-03-14 02:57:133517 web::NavigationItemList backwardItems =
3518 [tab navigationManager]->GetBackwardItems();
sdefresnee65fd872016-12-19 13:38:133519 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133520 withItems:backwardItems
sdefresnee65fd872016-12-19 13:38:133521 forBackHistory:YES];
3522}
3523
3524- (void)showTabHistoryPopupForForwardHistory {
3525 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133526
3527 // Dismiss the omnibox (if open).
3528 [_toolbarController cancelOmniboxEdit];
3529 // Dismiss the soft keyboard (if open).
3530 Tab* tab = [_model currentTab];
3531 [tab.webController dismissKeyboard];
3532
kkhorimoto1c12cbf2017-03-14 02:57:133533 web::NavigationItemList forwardItems =
3534 [tab navigationManager]->GetForwardItems();
sdefresnee65fd872016-12-19 13:38:133535 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133536 withItems:forwardItems
sdefresnee65fd872016-12-19 13:38:133537 forBackHistory:NO];
3538}
3539
3540- (void)navigateToSelectedEntry:(id)sender {
3541 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
3542 TabHistoryCell* selectedCell = (TabHistoryCell*)sender;
kkhorimoto7aed9e262017-03-04 02:28:553543 [[_model currentTab] goToItem:selectedCell.item];
sdefresnee65fd872016-12-19 13:38:133544 [_toolbarController dismissTabHistoryPopup];
3545}
3546
3547- (void)print {
3548 Tab* currentTab = [_model currentTab];
3549 // The UI should prevent users from printing non-printable pages. However, a
3550 // redirection to an un-printable page can happen before it is reflected in
3551 // the UI.
3552 if (![currentTab viewForPrinting]) {
pinkerton07e27842017-03-02 15:29:023553 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
sdefresnee65fd872016-12-19 13:38:133554 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
3555 return;
3556 }
3557 DCHECK(_browserState);
stkhapuginc9eee7b2017-04-10 15:49:273558 if (!_printController) {
3559 _printController = [[PrintController alloc]
3560 initWithContextGetter:_browserState->GetRequestContext()];
sdefresnee65fd872016-12-19 13:38:133561 }
3562 [_printController printView:[currentTab viewForPrinting]
3563 withTitle:[currentTab title]
3564 viewController:self];
3565}
3566
3567- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133568 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3569
3570 ReadingListModel* readingModel =
3571 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013572 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3573 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133574
pinkerton07e27842017-03-02 15:29:023575 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033576 [self showSnackbar:l10n_util::GetNSString(
3577 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133578}
3579
3580#pragma mark - Keyboard commands management
3581
3582- (BOOL)shouldRegisterKeyboardCommands {
3583 if ([self presentedViewController])
3584 return NO;
3585
3586 if (_voiceSearchController && _voiceSearchController->IsVisible())
3587 return NO;
3588
3589 // If there is no first responder, try to make the webview the first
3590 // responder.
3591 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273592 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133593 }
3594
3595 return YES;
3596}
3597
3598- (KeyCommandsProvider*)keyCommandsProvider {
3599 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273600 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133601 }
stkhapuginc9eee7b2017-04-10 15:49:273602 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133603}
3604
3605#pragma mark - KeyCommandsPlumbing
3606
3607- (BOOL)isOffTheRecord {
3608 return _isOffTheRecord;
3609}
3610
3611- (NSUInteger)tabsCount {
3612 return [_model count];
3613}
3614
lpromero47ea8862017-01-13 17:51:063615- (BOOL)canGoBack {
3616 return [_model currentTab].canGoBack;
3617}
3618
3619- (BOOL)canGoForward {
3620 return [_model currentTab].canGoForward;
3621}
3622
sdefresnee65fd872016-12-19 13:38:133623- (void)focusTabAtIndex:(NSUInteger)index {
3624 if ([_model count] > index) {
3625 [_model setCurrentTab:[_model tabAtIndex:index]];
3626 }
3627}
3628
3629- (void)focusNextTab {
3630 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3631 NSInteger modelCount = [_model count];
3632 if (currentTabIndex < modelCount - 1) {
3633 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3634 [_model setCurrentTab:nextTab];
3635 } else {
3636 [_model setCurrentTab:[_model tabAtIndex:0]];
3637 }
3638}
3639
3640- (void)focusPreviousTab {
3641 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3642 if (currentTabIndex > 0) {
3643 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3644 [_model setCurrentTab:previousTab];
3645 } else {
3646 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3647 [_model setCurrentTab:lastTab];
3648 }
3649}
3650
3651- (void)reopenClosedTab {
3652 sessions::TabRestoreService* const tabRestoreService =
3653 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3654 if (!tabRestoreService || tabRestoreService->entries().empty())
3655 return;
3656
3657 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3658 tabRestoreService->entries().front();
3659 // Only handle the TAB type.
3660 if (entry->type != sessions::TabRestoreService::TAB)
3661 return;
3662
3663 [self chromeExecuteCommand:[GenericChromeCommand commandWithTag:IDC_NEW_TAB]];
3664 TabRestoreServiceDelegateImplIOS* const delegate =
3665 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3666 _browserState);
3667 tabRestoreService->RestoreEntryById(delegate, entry->id,
3668 WindowOpenDisposition::CURRENT_TAB);
3669}
3670
3671- (void)focusOmnibox {
3672 [_toolbarController focusOmnibox];
3673}
3674
3675#pragma mark - UIResponder
3676
3677- (NSArray*)keyCommands {
3678 if (![self shouldRegisterKeyboardCommands]) {
3679 return nil;
3680 }
3681 return [self.keyCommandsProvider
3682 keyCommandsForConsumer:self
3683 editingText:![self isFirstResponder]];
3684}
3685
3686#pragma mark -
3687
3688// Induce an intentional crash in the browser process.
3689- (void)induceBrowserCrash {
3690 CHECK(false);
3691 // Call another function, so that the above CHECK can't be tail-call
3692 // optimized. This ensures that this method's name will show up in the stack
3693 // for easier identification.
3694 CHECK(true);
3695}
3696
3697- (void)loadURL:(const GURL&)url
3698 referrer:(const web::Referrer&)referrer
3699 transition:(ui::PageTransition)transition
3700 rendererInitiated:(BOOL)rendererInitiated {
3701 [[OmniboxGeolocationController sharedInstance]
3702 locationBarDidSubmitURL:url
3703 transition:transition
3704 browserState:_browserState];
3705
3706 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3707 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3708 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3709 }
3710
3711 // NOTE: This check for the Crash Host URL is here to avoid the URL from
3712 // ending up in the history causign the app to crash at every subsequent
3713 // restart.
3714 if (url.host() == kChromeUIBrowserCrashHost) {
3715 [self induceBrowserCrash];
3716 // In debug the app can continue working even after the CHECK. Adding a
3717 // return avoids the crash url to be added to the history.
3718 return;
3719 }
3720
3721 if (url == [_preloadController prerenderedURL]) {
sdefresne2c600c52017-04-04 16:49:593722 std::unique_ptr<web::WebState> newWebState =
3723 [_preloadController releasePrerenderContents];
3724 DCHECK(newWebState);
3725
sdefresnee65fd872016-12-19 13:38:133726 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593727 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133728 DCHECK(oldTab);
3729 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593730
kkhorimotod804c5732017-03-15 23:44:523731 bool canPruneItems =
3732 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593733
kkhorimotod804c5732017-03-15 23:44:523734 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523735 [newTab navigationManager]->CopyStateFromAndPrune(
3736 [oldTab navigationManager]);
sdefresnee65fd872016-12-19 13:38:133737 [[newTab nativeAppNavigationController]
3738 copyStateFrom:[oldTab nativeAppNavigationController]];
sdefresne2c600c52017-04-04 16:49:593739
3740 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3741 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133742
3743 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3744 // BrowserViewController to detect that a pre-rendered tab is switched in,
3745 // and show the prerendering animation.
3746 newTab.isPrerenderTab = NO;
3747
sdefresne2f7781c2017-03-02 19:12:463748 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133749 return;
3750 }
3751 }
3752
3753 GURL urlToLoad = url;
3754 if ([_preloadController hasPrefetchedURL:url]) {
3755 // Prefetched URLs have modified URLs, so load the prefetched version of
3756 // |url| instead of the original |url|.
3757 urlToLoad = [_preloadController prefetchedURL];
3758 }
3759
3760 [_preloadController cancelPrerender];
3761
3762 // Some URLs are not allowed while in incognito. If we are in incognito and
3763 // load a disallowed URL, instead create a new tab not in the incognito state.
3764 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3765 [self webPageOrderedOpen:url
3766 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133767 inIncognito:NO
3768 inBackground:NO
3769 appendTo:kCurrentTab];
3770 return;
3771 }
3772
3773 web::NavigationManager::WebLoadParams params(urlToLoad);
3774 params.referrer = referrer;
3775 params.transition_type = transition;
3776 params.is_renderer_initiated = rendererInitiated;
3777 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233778 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133779}
3780
3781- (void)loadJavaScriptFromLocationBar:(NSString*)script {
3782 [_preloadController cancelPrerender];
3783 DCHECK([_model currentTab]);
3784 [[_model currentTab].webController executeUserJavaScript:script
3785 completionHandler:nil];
3786}
3787
3788- (web::WebState*)currentWebState {
3789 return [[_model currentTab] webState];
3790}
3791
3792// This is called from within an animation block.
3793- (void)toolbarHeightChanged {
3794 if ([self headerHeight] != 0) {
3795 // Ensure full screen height is updated.
3796 Tab* currentTab = [_model currentTab];
3797 BOOL visible = self.isToolbarOnScreen;
3798 [currentTab updateFullscreenWithToolbarVisible:visible];
3799 }
3800}
3801
3802// Load a new URL on a new page/tab.
3803- (void)webPageOrderedOpen:(const GURL&)URL
3804 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133805 inBackground:(BOOL)inBackground
3806 appendTo:(OpenPosition)appendTo {
3807 Tab* adjacentTab = nil;
3808 if (appendTo == kCurrentTab)
3809 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353810 [_model insertTabWithURL:URL
3811 referrer:referrer
3812 transition:ui::PAGE_TRANSITION_LINK
3813 opener:adjacentTab
3814 openedByDOM:NO
3815 atIndex:TabModelConstants::kTabPositionAutomatically
3816 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133817}
3818
3819- (void)webPageOrderedOpen:(const GURL&)url
3820 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133821 inIncognito:(BOOL)inIncognito
3822 inBackground:(BOOL)inBackground
3823 appendTo:(OpenPosition)appendTo {
3824 if (inIncognito == _isOffTheRecord) {
3825 [self webPageOrderedOpen:url
3826 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133827 inBackground:inBackground
3828 appendTo:appendTo];
3829 return;
3830 }
3831 // When sending an open command that switches modes, ensure the tab
3832 // ends up appended to the end of the model, not just next to what is
3833 // currently selected in the other mode. This is done with the |append|
3834 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273835 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133836 initWithURL:url
3837 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133838 inIncognito:inIncognito
3839 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273840 appendTo:kLastTab];
sdefresnee65fd872016-12-19 13:38:133841 [self chromeExecuteCommand:command];
3842}
3843
3844- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
3845 [[_model currentTab] loadSessionTab:sessionTab];
3846}
3847
3848- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433849 DCHECK(javascript);
3850 javascript = [javascript stringByRemovingPercentEncoding];
3851 web::WebState* webState = [[_model currentTab] webState];
3852 if (webState) {
3853 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3854 }
sdefresnee65fd872016-12-19 13:38:133855}
3856
3857#pragma mark - WebToolbarDelegate methods
3858
3859- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3860 if (_locationBarHasFocus)
3861 return; // TODO(crbug.com/244366): This should not be necessary.
3862 _locationBarHasFocus = YES;
3863 [[NSNotificationCenter defaultCenter]
3864 postNotificationName:ios_internal::
3865 kLocationBarBecomesFirstResponderNotification
3866 object:nil];
3867 [_sideSwipeController setEnabled:NO];
3868 if ([[_model currentTab].webController wantsKeyboardShield]) {
3869 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3870 [_typingShield setAlpha:0.0];
3871 [_typingShield setHidden:NO];
3872 [UIView animateWithDuration:0.3
3873 animations:^{
3874 [_typingShield setAlpha:1.0];
3875 }];
3876 }
3877 [[OmniboxGeolocationController sharedInstance]
3878 locationBarDidBecomeFirstResponder:_browserState];
3879}
3880
3881- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3882 if (!_locationBarHasFocus)
3883 return; // TODO(crbug.com/244366): This should not be necessary.
3884 _locationBarHasFocus = NO;
3885 [_sideSwipeController setEnabled:YES];
3886 [[NSNotificationCenter defaultCenter]
3887 postNotificationName:ios_internal::
3888 kLocationBarResignsFirstResponderNotification
3889 object:nil];
3890 [UIView animateWithDuration:0.3
3891 animations:^{
3892 [_typingShield setAlpha:0.0];
3893 }
3894 completion:^(BOOL finished) {
3895 // This can happen if one quickly resigns the omnibox and then taps
3896 // on the omnibox again during this animation. If the animation is
3897 // interrupted and the toolbar controller is first responder, it's safe
3898 // to assume the |_typingShield| shouldn't be hidden here.
3899 if (!finished && [_toolbarController isOmniboxFirstResponder])
3900 return;
3901 [_typingShield setHidden:YES];
3902 }];
3903 [[OmniboxGeolocationController sharedInstance]
3904 locationBarDidResignFirstResponder:_browserState];
3905
3906 // If a load was cancelled by an omnibox edit, but nothing is loading when
3907 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3908 if (_locationBarEditCancelledLoad) {
3909 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:293910
3911 web::WebState* webState = [_model currentTab].webState;
3912 if (!_toolbarModelIOS->IsLoading() && webState)
3913 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3914 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133915 }
3916}
3917
3918- (IBAction)locationBarBeganEdit:(id)sender {
3919 // On handsets, if a page is currently loading it should be stopped.
3920 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
stkhapuginc9eee7b2017-04-10 15:49:273921 GenericChromeCommand* command =
3922 [[GenericChromeCommand alloc] initWithTag:IDC_STOP];
sdefresnee65fd872016-12-19 13:38:133923 [self chromeExecuteCommand:command];
3924 _locationBarEditCancelledLoad = YES;
3925 }
3926}
3927
3928- (IBAction)prepareToEnterTabSwitcher:(id)sender {
3929 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
3930}
3931
3932- (ToolbarModelIOS*)toolbarModelIOS {
3933 return _toolbarModelIOS.get();
3934}
3935
3936- (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
3937 [_toolbarController setBackgroundAlpha:alpha];
3938}
3939
3940- (void)updateToolbarControlsAlpha:(CGFloat)alpha {
3941 [_toolbarController setControlsAlpha:alpha];
3942}
3943
3944- (void)willUpdateToolbarSnapshot {
3945 [[_model currentTab].overscrollActionsController clear];
3946}
3947
3948- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
3949 CGRect frame = [_contentArea frame];
3950 if (!fullScreen) {
3951 // Changing the origin here is unnecessary, it's set in page_animation_util.
3952 frame.size.height -= [self headerHeight];
3953 }
3954
3955 CGFloat shortAxis = frame.size.width;
3956 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
3957 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
3958 CGFloat aspectRatio = frame.size.height / frame.size.width;
3959 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
3960 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
3961 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
3962 CGRect cardFrame = {frame.origin, cardSize};
3963
3964 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:173965 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133966 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
3967 : CardCloseButtonSide::LEADING;
3968 [_contentArea addSubview:card];
3969 return card;
3970}
3971
3972#pragma mark - Command Handling
3973
3974- (IBAction)chromeExecuteCommand:(id)sender {
3975 NSInteger command = [sender tag];
3976
3977 if (!_model || !_browserState)
3978 return;
rohitrao005a6432017-03-16 20:52:423979 Tab* currentTab = [_model currentTab];
sdefresnee65fd872016-12-19 13:38:133980
3981 switch (command) {
3982 case IDC_BACK:
lpromero47ea8862017-01-13 17:51:063983 [[_model currentTab] goBack];
sdefresnee65fd872016-12-19 13:38:133984 break;
3985 case IDC_BOOKMARK_PAGE:
3986 [self initializeBookmarkInteractionController];
3987 [_bookmarkInteractionController
3988 presentBookmarkForTab:[_model currentTab]
3989 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
3990 inView:[_toolbarController bookmarkButtonView]
3991 originRect:[_toolbarController bookmarkButtonAnchorRect]];
3992 break;
3993 case IDC_CLOSE_TAB:
3994 [self closeCurrentTab];
3995 break;
3996 case IDC_FIND:
3997 [self initFindBarForTab];
3998 break;
rohitraob2bf3cb2017-02-10 14:10:363999 case IDC_FIND_NEXT: {
rohitrao005a6432017-03-16 20:52:424000 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134001 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424002 FindTabHelper::FromWebState(currentTab.webState)
4003 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4004 [_findBarController updateResultsCount:model];
4005 });
sdefresnee65fd872016-12-19 13:38:134006 break;
rohitraob2bf3cb2017-02-10 14:10:364007 }
4008 case IDC_FIND_PREVIOUS: {
rohitrao005a6432017-03-16 20:52:424009 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134010 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424011 FindTabHelper::FromWebState(currentTab.webState)
4012 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4013 [_findBarController updateResultsCount:model];
4014 });
sdefresnee65fd872016-12-19 13:38:134015 break;
rohitraob2bf3cb2017-02-10 14:10:364016 }
sdefresnee65fd872016-12-19 13:38:134017 case IDC_FIND_CLOSE:
4018 [self closeFindInPage];
4019 break;
4020 case IDC_FIND_UPDATE:
4021 [self searchFindInPage];
4022 break;
4023 case IDC_FORWARD:
lpromero47ea8862017-01-13 17:51:064024 [[_model currentTab] goForward];
sdefresnee65fd872016-12-19 13:38:134025 break;
4026 case IDC_FULLSCREEN:
4027 NOTIMPLEMENTED();
4028 break;
4029 case IDC_HELP_PAGE_VIA_MENU:
4030 [self showHelpPage];
4031 break;
4032 case IDC_NEW_TAB:
4033 if (_isOffTheRecord) {
4034 // Not for this browser state, send it on its way.
4035 [super chromeExecuteCommand:sender];
4036 } else {
4037 [self newTab:sender];
4038 }
4039 break;
4040 case IDC_PRELOAD_VOICE_SEARCH:
4041 // Preload VoiceSearchController and views and view controllers needed
4042 // for voice search.
4043 [self ensureVoiceSearchControllerCreated];
4044 _voiceSearchController->PrepareToAppear();
4045 break;
4046 case IDC_NEW_INCOGNITO_TAB:
4047 if (_isOffTheRecord) {
4048 [self newTab:sender];
4049 } else {
4050 // Not for this browser state, send it on its way.
4051 [super chromeExecuteCommand:sender];
4052 }
4053 break;
liaoyuke563dc4a2017-03-17 18:36:294054 case IDC_RELOAD: {
4055 web::WebState* webState = [_model currentTab].webState;
4056 if (webState)
4057 // |check_for_repost| is true because the reload is explicitly initiated
4058 // by the user.
4059 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4060 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134061 break;
liaoyuke563dc4a2017-03-17 18:36:294062 }
sdefresnee65fd872016-12-19 13:38:134063 case IDC_SHARE_PAGE:
4064 [self sharePage];
4065 break;
4066 case IDC_SHOW_MAIL_COMPOSER:
4067 [self showMailComposer:sender];
4068 break;
4069 case IDC_READER_MODE:
4070 [[_model currentTab] switchToReaderMode];
4071 break;
4072 case IDC_REQUEST_DESKTOP_SITE:
liaoyuke6ab362012017-04-12 16:10:074073 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
sdefresnee65fd872016-12-19 13:38:134074 break;
liaoyukeea9f3ee62017-03-07 22:05:394075 case IDC_REQUEST_MOBILE_SITE:
liaoyuke6ab362012017-04-12 16:10:074076 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
liaoyukeea9f3ee62017-03-07 22:05:394077 break;
sdefresnee65fd872016-12-19 13:38:134078 case IDC_SHOW_TOOLS_MENU: {
jif7fed8122017-02-08 13:15:254079 [self showToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134080 break;
4081 }
4082 case IDC_SHOW_BOOKMARK_MANAGER: {
4083 if (IsIPadIdiom()) {
4084 [self showAllBookmarks];
4085 } else {
4086 [self initializeBookmarkInteractionController];
4087 [_bookmarkInteractionController presentBookmarks];
4088 }
4089 break;
4090 }
4091 case IDC_SHOW_OTHER_DEVICES: {
4092 if (IsIPadIdiom()) {
4093 [self showNTPPanel:NewTabPage::kOpenTabsPanel];
4094 } else {
4095 UIViewController* controller = [RecentTabsPanelViewController
4096 controllerToPresentForBrowserState:_browserState
4097 loader:self];
4098 controller.modalPresentationStyle = UIModalPresentationFormSheet;
4099 controller.modalPresentationCapturesStatusBarAppearance = YES;
4100 [self presentViewController:controller animated:YES completion:nil];
4101 }
4102 break;
4103 }
4104 case IDC_STOP:
sdefresne7d699dd2017-04-05 13:05:234105 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:134106 break;
4107#if !defined(NDEBUG)
4108 case IDC_VIEW_SOURCE:
4109 [self viewSource];
4110 break;
4111#endif
4112 case IDC_SHOW_PAGE_INFO:
4113 DCHECK([sender isKindOfClass:[UIButton class]]);
4114 [self showPageInfoPopupForView:sender];
4115 break;
4116 case IDC_HIDE_PAGE_INFO:
4117 [[NSNotificationCenter defaultCenter]
4118 postNotificationName:ios_internal::kPageInfoWillHideNotification
4119 object:nil];
4120 [self hidePageInfoPopupForView:sender];
4121 break;
4122 case IDC_SHOW_SECURITY_HELP:
4123 [self showSecurityHelpPage];
4124 break;
4125 case IDC_SHOW_BACK_HISTORY:
4126 [self showTabHistoryPopupForBackwardHistory];
4127 break;
4128 case IDC_SHOW_FORWARD_HISTORY:
4129 [self showTabHistoryPopupForForwardHistory];
4130 break;
4131 case IDC_BACK_FORWARD_IN_TAB_HISTORY:
4132 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
4133 [self navigateToSelectedEntry:sender];
4134 break;
4135 case IDC_PRINT:
4136 [self print];
4137 break;
4138 case IDC_ADD_READING_LIST: {
sdefresnee65fd872016-12-19 13:38:134139 ReadingListAddCommand* command =
4140 base::mac::ObjCCastStrict<ReadingListAddCommand>(sender);
4141 [self addToReadingListURL:[command URL] title:[command title]];
4142 break;
4143 }
4144 case IDC_RATE_THIS_APP:
4145 [self showRateThisAppDialog];
4146 break;
4147 case IDC_SHOW_READING_LIST:
sdefresnee65fd872016-12-19 13:38:134148 [self showReadingList];
4149 break;
4150 case IDC_VOICE_SEARCH:
4151 // If the voice search command is coming from a UIView sender, store it
4152 // before sending the command up the responder chain.
4153 if ([sender isKindOfClass:[UIView class]])
stkhapuginc9eee7b2017-04-10 15:49:274154 _voiceSearchButton = sender;
sdefresnee65fd872016-12-19 13:38:134155 [super chromeExecuteCommand:sender];
4156 break;
4157 case IDC_SHOW_QR_SCANNER:
jif5f067c62017-02-03 17:36:434158 [self showQRScanner];
sdefresnee65fd872016-12-19 13:38:134159 break;
gambardd2e44fb2017-01-25 09:14:214160 case IDC_SHOW_SUGGESTIONS:
4161 if (experimental_flags::IsSuggestionsUIEnabled()) {
4162 [self showSuggestionsUI];
4163 }
4164 break;
sdefresnee65fd872016-12-19 13:38:134165 default:
4166 // Unknown commands get sent up the responder chain.
4167 [super chromeExecuteCommand:sender];
4168 break;
4169 }
4170}
4171
4172- (void)closeCurrentTab {
4173 Tab* currentTab = [_model currentTab];
4174 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4175 if (tabIndex == NSNotFound)
4176 return;
4177
jif7fed8122017-02-08 13:15:254178 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4179 // iPad.
sdefresnee65fd872016-12-19 13:38:134180 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4181 exitingPage.image =
4182 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4183
4184 // Close the actual tab, and add its image as a subview.
4185 [_model closeTabAtIndex:tabIndex];
4186
4187 // Do not animate close in iPad.
4188 if (!IsIPadIdiom()) {
4189 [_contentArea addSubview:exitingPage];
4190 ios_internal::page_animation_util::AnimateOutWithCompletion(
4191 exitingPage, 0, YES, IsPortrait(), ^{
4192 [exitingPage removeFromSuperview];
4193 });
4194 }
4195}
4196
sdefresnee65fd872016-12-19 13:38:134197- (void)sharePage {
jif4a8cf942017-02-03 12:05:244198 ShareToData* data = activity_services::ShareToDataForTab([_model currentTab]);
sdefresnee65fd872016-12-19 13:38:134199 if (data)
4200 [self sharePageWithData:data];
4201}
4202
4203- (void)sharePageWithData:(ShareToData*)data {
4204 id<ShareProtocol> controller = [_dependencyFactory shareControllerInstance];
4205 if ([controller isActive])
4206 return;
4207 CGRect fromRect = [_toolbarController shareButtonAnchorRect];
4208 UIView* inView = [_toolbarController shareButtonView];
4209 [controller shareWithData:data
4210 controller:self
4211 browserState:_browserState
4212 shareToDelegate:self
4213 fromRect:fromRect
4214 inView:inView];
4215}
4216
4217- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
4218 [[_dependencyFactory shareControllerInstance] cancelShareAnimated:NO];
4219 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4220 [_bookmarkInteractionController dismissSnackbar];
4221 [_toolbarController cancelOmniboxEdit];
4222 [_dialogPresenter cancelAllDialogs];
4223 [self hidePageInfoPopupForView:nil];
4224 if (_voiceSearchController)
4225 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364226
4227 Tab* currentTab = [_model currentTab];
4228 [currentTab dismissModals];
4229
rohitrao005a6432017-03-16 20:52:424230 if (currentTab) {
4231 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4232 if (findHelper) {
4233 findHelper->StopFinding(^{
4234 [self updateFindBar:NO shouldFocus:NO];
4235 });
4236 }
4237 }
rohitraob2bf3cb2017-02-10 14:10:364238
sdefresnee65fd872016-12-19 13:38:134239 [_contextualSearchController movePanelOffscreen];
sdefresnee65fd872016-12-19 13:38:134240 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134241 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274242 _printController = nil;
jif7fed8122017-02-08 13:15:254243 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134244 [_contextMenuCoordinator stop];
4245 [self dismissRateThisAppDialog];
4246
gambardd2e44fb2017-01-25 09:14:214247 [_contentSuggestionsCoordinator stop];
4248
sdefresnee65fd872016-12-19 13:38:134249 if (self.presentedViewController) {
4250 // Dismisses any other modal controllers that may be present, e.g. Recent
4251 // Tabs.
4252 // Note that currently, some controllers like the bookmark ones were already
4253 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4254 // but are still reported as the presentedViewController. The result is that
4255 // this will call -dismissViewControllerAnimated:completion: a second time
4256 // on it. It is not per se an issue, as it is a no-op. The problem is that
4257 // in such a case, the completion block is not called.
4258 // To ensure the completion is called, nil is passed here, and the
4259 // completion is called below.
4260 [self dismissViewControllerAnimated:NO completion:nil];
4261 // Dismissed controllers will be so after a delay. Queue the completion
4262 // callback after that.
4263 if (completion) {
4264 dispatch_after(
4265 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4266 dispatch_get_main_queue(), ^{
4267 completion();
4268 });
4269 }
4270 } else if (completion) {
4271 // If no view controllers are presented, we should be ok with dispatching
4272 // the completion block directly.
4273 dispatch_async(dispatch_get_main_queue(), completion);
4274 }
4275}
4276
4277- (void)showHelpPage {
4278 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4279 [self webPageOrderedOpen:helpUrl
4280 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134281 inBackground:NO
4282 appendTo:kCurrentTab];
4283}
4284
sdefresnee65fd872016-12-19 13:38:134285- (void)resetAllWebViews {
eugenebutf8a138e62017-01-24 22:41:344286 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:134287 [_model resetAllWebViews];
4288}
4289
4290#pragma mark - Find Bar
4291
4292- (void)hideFindBarWithAnimation:(BOOL)animate {
4293 [_findBarController hideFindBarView:animate];
4294}
4295
4296- (void)showFindBarWithAnimation:(BOOL)animate
4297 selectText:(BOOL)selectText
4298 shouldFocus:(BOOL)shouldFocus {
4299 DCHECK(_findBarController);
4300 Tab* tab = [_model currentTab];
4301 DCHECK(tab);
4302 CRWWebController* webController = tab.webController;
4303
4304 CGRect referenceFrame = CGRectZero;
4305 if (IsIPadIdiom()) {
4306 referenceFrame = webController.visibleFrame;
4307 referenceFrame.origin.y -= kIPadFindBarOverlap;
4308 } else {
4309 referenceFrame = _contentArea.frame;
4310 }
4311
4312 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4313 [_findBarController addFindBarView:animate
4314 intoView:self.view
4315 withFrame:referenceFrame
4316 alignWithFrame:omniboxFrame
4317 selectText:selectText];
4318 [self updateFindBar:YES shouldFocus:shouldFocus];
4319}
4320
4321// Create find bar controller and pass it to the web controller.
4322- (void)initFindBarForTab {
4323 if (!self.canShowFindBar)
4324 return;
4325
4326 if (!_findBarController)
stkhapuginc9eee7b2017-04-10 15:49:274327 _findBarController =
4328 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134329
4330 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:424331 DCHECK(tab);
4332 auto* helper = FindTabHelper::FromWebState(tab.webState);
4333 DCHECK(!helper->IsFindUIActive());
4334 helper->SetFindUIActive(true);
sdefresnee65fd872016-12-19 13:38:134335 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4336}
4337
4338- (void)searchFindInPage {
rohitrao005a6432017-03-16 20:52:424339 DCHECK([_model currentTab]);
4340 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
stkhapuginc9eee7b2017-04-10 15:49:274341 __weak BrowserViewController* weakSelf = self;
4342 helper->StartFinding(
4343 [_findBarController searchTerm], ^(FindInPageModel* model) {
4344 BrowserViewController* strongSelf = weakSelf;
4345 if (!strongSelf) {
4346 return;
4347 }
4348 [strongSelf->_findBarController updateResultsCount:model];
4349 });
rohitrao005a6432017-03-16 20:52:424350
sdefresnee65fd872016-12-19 13:38:134351 if (!_isOffTheRecord)
rohitrao005a6432017-03-16 20:52:424352 helper->PersistSearchTerm();
sdefresnee65fd872016-12-19 13:38:134353}
4354
4355- (void)closeFindInPage {
stkhapuginc9eee7b2017-04-10 15:49:274356 __weak BrowserViewController* weakSelf = self;
rohitrao005a6432017-03-16 20:52:424357 Tab* currentTab = [_model currentTab];
4358 if (currentTab) {
4359 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4360 [weakSelf updateFindBar:NO shouldFocus:NO];
4361 });
4362 }
sdefresnee65fd872016-12-19 13:38:134363}
4364
4365- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
rohitrao005a6432017-03-16 20:52:424366 DCHECK([_model currentTab]);
4367 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4368 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134369 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424370 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134371 }
4372
4373 [self setFramesForHeaders:[self headerViews]
4374 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424375 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134376 initialUpdate:initialUpdate
4377 focusTextfield:shouldFocus];
4378 } else {
4379 [self hideFindBarWithAnimation:YES];
4380 }
4381}
4382
4383- (void)showAllBookmarks {
4384 DCHECK(self.visible || self.dismissingModal);
4385 GURL URL(kChromeUIBookmarksURL);
4386 Tab* tab = [_model currentTab];
4387 web::NavigationManager::WebLoadParams params(URL);
4388 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234389 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134390}
4391
4392- (void)showReadingList {
stkhapuginc9eee7b2017-04-10 15:49:274393 _readingListCoordinator = [[ReadingListCoordinator alloc]
gambard6299cc1d2017-02-21 13:06:034394 initWithBaseViewController:self
4395 browserState:self.browserState
stkhapuginc9eee7b2017-04-10 15:49:274396 loader:self];
gambard6299cc1d2017-02-21 13:06:034397
4398 [_readingListCoordinator start];
sdefresnee65fd872016-12-19 13:38:134399}
4400
4401- (void)showQRScanner {
stkhapuginc9eee7b2017-04-10 15:49:274402 _qrScannerViewController =
4403 [[QRScannerViewController alloc] initWithDelegate:_toolbarController];
sdefresnee65fd872016-12-19 13:38:134404 [self presentViewController:[_qrScannerViewController
4405 getViewControllerToPresent]
4406 animated:YES
4407 completion:nil];
4408}
4409
gambardd2e44fb2017-01-25 09:14:214410- (void)showSuggestionsUI {
4411 if (!_contentSuggestionsCoordinator) {
stkhapuginc9eee7b2017-04-10 15:49:274412 _contentSuggestionsCoordinator =
4413 [[ContentSuggestionsCoordinator alloc] initWithBaseViewController:self];
gambard9b6abde2017-02-20 12:13:554414 [_contentSuggestionsCoordinator setURLLoader:self];
gambardd2e44fb2017-01-25 09:14:214415 }
gambard0ac7f3e2017-02-01 14:53:144416 [_contentSuggestionsCoordinator setBrowserState:_browserState];
gambardd2e44fb2017-01-25 09:14:214417 [_contentSuggestionsCoordinator start];
4418}
4419
sdefresnee65fd872016-12-19 13:38:134420- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel {
4421 DCHECK(self.visible || self.dismissingModal);
4422 GURL url(kChromeUINewTabURL);
4423 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4424 if (fragment != "") {
4425 GURL::Replacements replacement;
4426 replacement.SetRefStr(fragment);
4427 url = url.ReplaceComponents(replacement);
4428 }
4429 Tab* tab = [_model currentTab];
4430 web::NavigationManager::WebLoadParams params(url);
4431 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234432 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134433}
4434
4435- (void)showRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274436 DCHECK(!_rateThisAppDialog);
sdefresnee65fd872016-12-19 13:38:134437
4438 // Store the current timestamp whenever this dialog is shown.
4439 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4440 base::Time::Now().ToInternalValue());
4441
4442 // Some versions of iOS7 do not support linking directly to the "Ratings and
4443 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4444 // links to the main appstore page for the Chrome app.
4445 NSURL* storeURL =
4446 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4447 @"MZStore.woa/wa/"
4448 @"viewContentsUserReviews?type=Purple+Software&id="
4449 @"535886823&pt=9008&ct=rating")];
4450
4451 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4452 [self clearPresentedStateWithCompletion:nil];
4453
stkhapuginc9eee7b2017-04-10 15:49:274454 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
sdefresnee65fd872016-12-19 13:38:134455 [_rateThisAppDialog setAppStoreURL:storeURL];
4456 [_rateThisAppDialog setDelegate:self];
4457 [_rateThisAppDialog show];
4458}
4459
4460- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274461 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134462 base::RecordAction(base::UserMetricsAction(
4463 "IOSRateThisAppDialogDismissedProgramatically"));
4464 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274465 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134466 }
4467}
4468
4469#if !defined(NDEBUG)
4470- (void)viewSource {
4471 Tab* tab = [_model currentTab];
4472 DCHECK(tab);
4473 CRWWebController* webController = tab.webController;
4474 NSString* script = @"document.documentElement.outerHTML;";
stkhapuginc9eee7b2017-04-10 15:49:274475 __weak Tab* weakTab = tab;
4476 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:134477 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
stkhapuginc9eee7b2017-04-10 15:49:274478 Tab* strongTab = weakTab;
sdefresnee65fd872016-12-19 13:38:134479 if (!strongTab)
4480 return;
4481 if (![result isKindOfClass:[NSString class]])
4482 result = @"Not an HTML page";
4483 std::string base64HTML;
4484 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4485 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
4486 web::Referrer referrer([strongTab url], web::ReferrerPolicyDefault);
eugenebut91cfb3a2017-02-21 16:40:314487
4488 [[weakSelf tabModel]
sdefresnea6395912017-03-01 01:14:354489 insertTabWithURL:URL
4490 referrer:referrer
4491 transition:ui::PAGE_TRANSITION_LINK
4492 opener:strongTab
4493 openedByDOM:YES
4494 atIndex:TabModelConstants::kTabPositionAutomatically
4495 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:134496 };
4497 [webController executeJavaScript:script
4498 completionHandler:completionHandlerBlock];
4499}
4500#endif // !defined(NDEBUG)
4501
4502- (void)startVoiceSearch {
4503 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124504 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134505 _startVoiceSearchAfterNewTabAnimation = YES;
4506 return;
4507 }
4508
4509 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4510 // dismiss the keyboard.
4511 [self closeFindInPage];
4512 [[_model currentTab].webController dismissKeyboard];
4513
4514 // Ensure that voice search objects are created.
4515 [self ensureVoiceSearchControllerCreated];
4516 [self ensureVoiceSearchBarCreated];
4517
4518 // Present voice search.
4519 [_voiceSearchBar prepareToPresentVoiceSearch];
4520 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4521 [_toolbarController cancelOmniboxEdit];
4522}
4523
4524#pragma mark - ToolbarOwner
4525
4526- (ToolbarController*)relinquishedToolbarController {
4527 if (_isToolbarControllerRelinquished)
4528 return nil;
4529
4530 ToolbarController* relinquishedToolbarController = nil;
4531 if ([_toolbarController view].hidden) {
4532 Tab* currentTab = [_model currentTab];
4533 if (currentTab && UrlHasChromeScheme(currentTab.url)) {
4534 // Use the native content controller's toolbar when the BVC's is hidden.
4535 id nativeController = [self nativeControllerForTab:currentTab];
4536 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4537 relinquishedToolbarController =
4538 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274539 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134540 }
4541 }
4542 } else {
stkhapuginc9eee7b2017-04-10 15:49:274543 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134544 }
4545 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4546 return relinquishedToolbarController;
4547}
4548
4549- (void)reparentToolbarController {
4550 if (_isToolbarControllerRelinquished) {
4551 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4552 // A native content controller's toolbar has been relinquished.
4553 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274554 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134555 } else if ([_findBarController isFindInPageShown]) {
4556 [self.view insertSubview:[_toolbarController view]
4557 belowSubview:[_findBarController view]];
4558 } else {
4559 [self.view addSubview:[_toolbarController view]];
4560 }
4561 if (_contextualSearchPanel) {
4562 // Move panel back into its correct place.
4563 [self.view insertSubview:_contextualSearchPanel
4564 aboveSubview:[_toolbarController view]];
4565 }
4566 _isToolbarControllerRelinquished = NO;
4567 }
4568}
4569
4570#pragma mark - TabModelObserver methods
4571
4572// Observer method, tab inserted.
4573- (void)tabModel:(TabModel*)model
4574 didInsertTab:(Tab*)tab
4575 atIndex:(NSUInteger)modelIndex
4576 inForeground:(BOOL)fg {
4577 DCHECK(tab);
4578 [self installDelegatesForTab:tab];
4579
4580 if (fg) {
4581 [_contextualSearchController setTab:tab];
4582 [_paymentRequestManager setWebState:tab.webState];
4583 }
4584}
4585
4586// Observer method, active tab changed.
4587- (void)tabModel:(TabModel*)model
4588 didChangeActiveTab:(Tab*)newTab
4589 previousTab:(Tab*)previousTab
4590 atIndex:(NSUInteger)index {
4591 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4592 // Currently this observer method is always called with a non-nil |newTab|,
4593 // but that may change in the future. Remove this DCHECK when it does.
4594 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274595 if (_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:134596 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4597 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4598 }
4599 [self updateVoiceSearchBarVisibilityAnimated:NO];
4600
4601 [_contextualSearchController setTab:newTab];
4602 [_paymentRequestManager setWebState:newTab.webState];
4603
4604 [self tabSelected:newTab];
4605 DCHECK_EQ(newTab, [model currentTab]);
4606 [self installDelegatesForTab:newTab];
4607}
4608
4609// Observer method, tab changed.
4610- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4611 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4612 if (tab == [_model currentTab]) {
4613 [self updateToolbar];
4614 // Disable contextual search when |tab| is a voice search result tab.
4615 BOOL enableContextualSearch = self.active && !tab.isVoiceSearchResultsTab;
4616 [_contextualSearchController enableContextualSearch:enableContextualSearch];
4617 }
4618}
4619
sdefresne49cf2862017-03-15 13:46:144620// Observer method, tab replaced.
4621- (void)tabModel:(TabModel*)model
4622 didReplaceTab:(Tab*)oldTab
4623 withTab:(Tab*)newTab
4624 atIndex:(NSUInteger)index {
4625 [self uninstallDelegatesForTab:oldTab];
4626 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264627
4628 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4629 if (self.active && model.currentTab == newTab)
4630 [self displayTab:newTab isNewSelection:NO];
sdefresne49cf2862017-03-15 13:46:144631}
4632
sdefresnee65fd872016-12-19 13:38:134633// A tab has been removed, remove its views from display if necessary.
4634- (void)tabModel:(TabModel*)model
4635 didRemoveTab:(Tab*)tab
4636 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144637 [self uninstallDelegatesForTab:tab];
4638
sdefresnee65fd872016-12-19 13:38:134639 // Remove stored native controllers for the tab.
4640 [_nativeControllersForTabIDs removeObjectForKey:tab.tabId];
4641
4642 // Ignore changes while the tab stack view is visible (or while suspended).
4643 // The display will be refreshed when this view becomes active again.
4644 if (!self.visible || !model.webUsageEnabled)
4645 return;
4646
4647 // Remove the find bar for now.
4648 [self hideFindBarWithAnimation:NO];
4649}
4650
4651- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4652 if (tab == [model currentTab]) {
4653 [_contentArea displayContentView:nil];
4654 [_toolbarController selectedTabChanged];
4655 }
4656
4657 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4658 if ([model count] == 1) { // About to remove the last tab.
4659 [_contextualSearchController setTab:nil];
4660 [_paymentRequestManager setWebState:nil];
4661 }
4662}
4663
4664// Called when the number of tabs changes. Update the toolbar accordingly.
4665- (void)tabModelDidChangeTabCount:(TabModel*)model {
4666 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134667 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134668}
4669
4670#pragma mark - Upgrade Detection
4671
4672- (void)showUpgrade:(UpgradeCenter*)center {
4673 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274674 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134675 NSString* tabId = tab.tabId;
4676 DCHECK([tab infoBarManager]);
4677 [center addInfoBarToManager:[tab infoBarManager] forTabId:tabId];
4678 }
4679}
4680
4681#pragma mark - ContextualSearchControllerDelegate
4682
4683- (void)createTabFromContextualSearchController:(const GURL&)url {
4684 Tab* currentTab = [_model currentTab];
4685 DCHECK(currentTab);
4686 NSUInteger index = [_model indexOfTab:currentTab];
4687 [self addSelectedTabWithURL:url
4688 atIndex:index + 1
4689 transition:ui::PAGE_TRANSITION_LINK];
4690}
4691
4692- (void)promotePanelToTabProvidedBy:(id<ContextualSearchTabProvider>)tabProvider
4693 focusInput:(BOOL)focusInput {
4694 // Tell the panel it will be promoted.
4695 ContextualSearchPanelView* promotingPanel = _contextualSearchPanel;
4696 [promotingPanel prepareForPromotion];
4697
4698 // Make a new panel and tell the controller about it.
4699 _contextualSearchPanel = [self createPanelView];
4700 [self.view insertSubview:_contextualSearchPanel belowSubview:promotingPanel];
4701 [_contextualSearchController setPanel:_contextualSearchPanel];
4702
4703 // Figure out vertical offset.
4704 CGFloat offset = StatusBarHeight();
4705 if (IsIPadIdiom()) {
4706 offset = MAX(offset, CGRectGetMaxY([_tabStripController view].frame));
4707 }
4708
4709 // Transition steps: Animate the panel position, fade in the toolbar and
4710 // tab strip.
4711 ProceduralBlock transition = ^{
4712 [promotingPanel promoteToMatchSuperviewWithVerticalOffset:offset];
4713 [self updateToolbarControlsAlpha:1.0];
4714 [self updateToolbarBackgroundAlpha:1.0];
4715 [_tabStripController view].alpha = 1.0;
4716 };
4717
4718 // After the transition animation completes, add the tab to the tab model
4719 // (on iPad this triggers the tab strip animation too), then fade out the
4720 // transitioning panel and remove it.
4721 void (^completion)(BOOL) = ^(BOOL finished) {
4722 _contextualSearchMask.alpha = 0;
sdefresne2c600c52017-04-04 16:49:594723 std::unique_ptr<web::WebState> webState = [tabProvider releaseWebState];
4724 DCHECK(webState);
4725 DCHECK(webState->GetNavigationManager());
4726
4727 Tab* newTab = LegacyTabHelper::GetTabForWebState(webState.get());
4728 WebStateList* webStateList = [_model webStateList];
4729
4730 // Insert the new tab after the current tab.
4731 DCHECK_NE(webStateList->active_index(), WebStateList::kInvalidIndex);
4732 DCHECK_NE(webStateList->active_index(), INT_MAX);
4733 int insertion_index = webStateList->active_index() + 1;
4734 webStateList->InsertWebState(insertion_index, std::move(webState));
4735 webStateList->SetOpenerOfWebStateAt(insertion_index,
4736 [tabProvider webStateOpener]);
sdefresnee65fd872016-12-19 13:38:134737
4738 // Set isPrerenderTab to NO after inserting the tab. This will allow the
4739 // BrowserViewController to detect that a pre-rendered tab is switched in,
4740 // and show the prerendering animation. This needs to happen before the
4741 // tab is made the current tab.
4742 // This also enables contextual search (if otherwise applicable) on
4743 // |newTab|.
sdefresne2c600c52017-04-04 16:49:594744
sdefresnee65fd872016-12-19 13:38:134745 newTab.isPrerenderTab = NO;
4746 [_model setCurrentTab:newTab];
4747
sdefresne2f7781c2017-03-02 19:12:464748 if (newTab.loadFinished)
sdefresnee65fd872016-12-19 13:38:134749 [self tabLoadComplete:newTab withSuccess:YES];
4750
4751 if (focusInput) {
4752 [_toolbarController focusOmnibox];
4753 }
4754 _infoBarContainer->RestoreInfobars();
4755
4756 [UIView animateWithDuration:ios::material::kDuration2
4757 animations:^{
4758 promotingPanel.alpha = 0;
4759 }
4760 completion:^(BOOL finished) {
4761 [promotingPanel removeFromSuperview];
4762 }];
4763 };
4764
4765 [UIView animateWithDuration:ios::material::kDuration3
4766 animations:transition
4767 completion:completion];
4768}
4769
4770- (ContextualSearchPanelView*)createPanelView {
4771 PanelConfiguration* config;
4772 CGSize panelContainerSize = self.view.bounds.size;
4773 if (IsIPadIdiom()) {
4774 config = [PadPanelConfiguration
4775 configurationForContainerSize:panelContainerSize
4776 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4777 } else {
4778 config = [PhonePanelConfiguration
4779 configurationForContainerSize:panelContainerSize
4780 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4781 }
stkhapuginf58b10d02017-04-10 13:36:174782 ContextualSearchPanelView* newPanel =
4783 [[ContextualSearchPanelView alloc] initWithConfiguration:config];
sdefresnee65fd872016-12-19 13:38:134784 [newPanel addMotionObserver:self];
4785 [newPanel addMotionObserver:_contextualSearchMask];
4786 return newPanel;
4787}
4788
4789#pragma mark - ContextualSearchPanelMotionObserver
4790
4791- (void)panel:(ContextualSearchPanelView*)panel
4792 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
4793 // If the header is offset, it's offscreen (or moving offscreen) and the
4794 // toolbar shouldn't be opacity-adjusted by the contextual search panel.
4795 if ([self currentHeaderOffset] != 0)
4796 return;
4797
4798 CGFloat toolbarAlpha;
4799
4800 if (motion.state == ContextualSearch::PREVIEWING) {
4801 // As the panel moves past the previewing position, the toolbar should
4802 // become more transparent.
4803 toolbarAlpha = 1 - motion.gradation;
4804 } else if (motion.state == ContextualSearch::COVERING) {
4805 // The toolbar should be totally transparent when the panel is covering.
4806 toolbarAlpha = 0.0;
4807 } else {
4808 return;
4809 }
4810
4811 // On iPad, the toolbar doesn't go fully transparent, so map |toolbarAlpha|'s
4812 // [0-1.0] range to [0.5-1.0].
4813 if (IsIPadIdiom()) {
4814 toolbarAlpha = 0.5 + (toolbarAlpha * 0.5);
4815 [_tabStripController view].alpha = toolbarAlpha;
4816 }
4817
4818 [self updateToolbarControlsAlpha:toolbarAlpha];
4819 [self updateToolbarBackgroundAlpha:toolbarAlpha];
4820}
4821
4822- (void)panel:(ContextualSearchPanelView*)panel
4823 didChangeToState:(ContextualSearch::PanelState)toState
4824 fromState:(ContextualSearch::PanelState)fromState {
4825 if (toState == ContextualSearch::DISMISSED) {
4826 // Panel has become hidden.
4827 _infoBarContainer->RestoreInfobars();
4828 [self updateToolbarControlsAlpha:1.0];
4829 [self updateToolbarBackgroundAlpha:1.0];
4830 [_tabStripController view].alpha = 1.0;
4831 } else if (fromState == ContextualSearch::DISMISSED) {
4832 // Panel has become visible.
4833 _infoBarContainer->SuspendInfobars();
4834 }
4835}
4836
4837- (void)panelWillPromote:(ContextualSearchPanelView*)panel {
4838 [panel removeMotionObserver:self];
4839}
4840
4841- (CGFloat)currentHeaderHeight {
4842 return [self headerHeight] - [self currentHeaderOffset];
4843}
4844
4845#pragma mark - InfoBarControllerDelegate
4846
4847- (void)infoBarContainerStateChanged:(bool)isAnimating {
4848 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4849 DCHECK(infoBarContainerView);
4850 CGRect containerFrame = infoBarContainerView.frame;
4851 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4852 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4853 containerFrame.size.height = height;
4854 BOOL isViewVisible = self.visible;
4855 [UIView animateWithDuration:0.1
4856 animations:^{
4857 [infoBarContainerView setFrame:containerFrame];
4858 }
4859 completion:^(BOOL finished) {
4860 if (!isViewVisible)
4861 return;
4862 UIAccessibilityPostNotification(
4863 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4864 }];
4865}
4866
4867- (BOOL)shouldAutorotate {
4868 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4869 // Don't rotate if a voice search is being presented or dismissed. Once the
4870 // transition animations finish, only the Voice Search UIViewController's
4871 // |-shouldAutorotate| will be called.
4872 return NO;
4873 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4874 // Don't auto rotate if side swipe controller view says not to.
4875 return NO;
4876 } else {
4877 return [super shouldAutorotate];
4878 }
4879}
4880
4881// Always return yes, as this tap should work with various recognizers,
4882// including UITextTapRecognizer, UILongPressGestureRecognizer,
4883// UIScrollViewPanGestureRecognizer and others.
4884- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4885 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4886 (UIGestureRecognizer*)otherGestureRecognizer {
4887 return YES;
4888}
4889
4890// Tap gestures should only be recognized within |_contentArea|.
4891- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4892 CGPoint location = [gesture locationInView:self.view];
4893
4894 // Only allow touches on descendant views of |_contentArea|.
4895 UIView* hitView = [self.view hitTest:location withEvent:nil];
4896 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4897}
4898
4899#pragma mark - SideSwipeController Delegate Methods
4900
4901- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4902 DCHECK(!IsIPadIdiom());
4903 // Update frame incase orientation changed while |_contentArea| was out of
4904 // the view hierarchy.
4905 [_contentArea setFrame:[sideSwipeView frame]];
4906
4907 [self.view insertSubview:_contentArea atIndex:0];
4908 [self updateVoiceSearchBarVisibilityAnimated:NO];
4909 [self updateToolbar];
4910
4911 // Reset horizontal stack view.
4912 [sideSwipeView removeFromSuperview];
4913 [_sideSwipeController setInSwipe:NO];
4914 [_infoBarContainer->view() setHidden:NO];
4915}
4916
4917- (UIView*)contentView {
4918 return _contentArea;
4919}
4920
4921- (TabStripController*)tabStripController {
4922 return _tabStripController;
4923}
4924
4925- (WebToolbarController*)toolbarController {
4926 return _toolbarController;
4927}
4928
4929- (BOOL)preventSideSwipe {
4930 if ([_toolbarController toolsPopupController])
4931 return YES;
4932
4933 if (_voiceSearchController && _voiceSearchController->IsVisible())
4934 return YES;
4935
4936 if ([_contextualSearchPanel state] >= ContextualSearch::PEEKING)
4937 return YES;
4938
4939 if (!self.active)
4940 return YES;
4941
4942 return NO;
4943}
4944
4945- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4946 if (visible) {
4947 [self updateVoiceSearchBarVisibilityAnimated:NO];
4948 [self updateToolbar];
4949 [_infoBarContainer->view() setHidden:NO];
4950 } else {
4951 // Hide UI accessories such as find bar and first visit overlays
4952 // for welcome page.
4953 [self hideFindBarWithAnimation:NO];
4954 [_infoBarContainer->view() setHidden:YES];
4955 [_voiceSearchBar setHidden:YES];
4956 }
4957}
4958
4959- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4960 BOOL seenToolbar = NO;
4961 BOOL seenInfoBarContainer = NO;
4962 BOOL seenContentArea = NO;
4963 for (UIView* view in views.subviews) {
4964 if (view == [_toolbarController view])
4965 seenToolbar = YES;
4966 else if (view == _infoBarContainer->view())
4967 seenInfoBarContainer = YES;
4968 else if (view == _contentArea)
4969 seenContentArea = YES;
4970 if ((seenToolbar && !seenInfoBarContainer) ||
4971 (seenInfoBarContainer && !seenContentArea))
4972 return NO;
4973 }
4974 return YES;
4975}
4976
4977#pragma mark - PreloadControllerDelegate methods
4978
4979- (BOOL)shouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444980 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134981}
4982
sdefresnee65fd872016-12-19 13:38:134983#pragma mark - BookmarkBridgeMethods
4984
4985// If an added or removed bookmark is the same as the current url, update the
4986// toolbar so the star highlight is kept in sync.
4987- (void)bookmarkNodeModified:(const BookmarkNode*)node {
4988 if ([_model currentTab] && node->url() == [_model currentTab].url)
4989 [self updateToolbar];
4990}
4991
4992// If all bookmarks are removed, update the toolbar so the star highlight is
4993// kept in sync.
4994- (void)allBookmarksRemoved {
4995 [self updateToolbar];
4996}
4997
4998#pragma mark - ShareToDelegate methods
4999
5000- (void)shareDidComplete:(ShareTo::ShareResult)shareStatus
jife5fcd332017-03-16 15:14:585001 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:135002 // The shareTo dialog dismisses itself instead of through
5003 // |-dismissViewControllerAnimated:completion:| so we must reset the
5004 // presenting state here.
5005 self.presenting = NO;
5006 [self.dialogPresenter tryToPresent];
5007
5008 switch (shareStatus) {
5009 case ShareTo::SHARE_SUCCESS:
pinkerton07e27842017-03-02 15:29:025010 if ([message length]) {
5011 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:135012 [self showSnackbar:message];
pinkerton07e27842017-03-02 15:29:025013 }
sdefresnee65fd872016-12-19 13:38:135014 break;
5015 case ShareTo::SHARE_ERROR:
5016 [self showErrorAlert:IDS_IOS_SHARE_TO_ERROR_ALERT_TITLE
5017 message:IDS_IOS_SHARE_TO_ERROR_ALERT];
5018 break;
5019 case ShareTo::SHARE_NETWORK_FAILURE:
5020 [self showErrorAlert:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT_TITLE
5021 message:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT];
5022 break;
5023 case ShareTo::SHARE_SIGN_IN_FAILURE:
5024 [self showErrorAlert:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT_TITLE
5025 message:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT];
5026 break;
5027 case ShareTo::SHARE_CANCEL:
5028 case ShareTo::SHARE_UNKNOWN_RESULT:
5029 break;
5030 }
5031}
5032
5033- (void)passwordAppExDidFinish:(ShareTo::ShareResult)shareStatus
5034 username:(NSString*)username
5035 password:(NSString*)password
jife5fcd332017-03-16 15:14:585036 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:135037 switch (shareStatus) {
5038 case ShareTo::SHARE_SUCCESS: {
5039 PasswordController* passwordController =
5040 [[_model currentTab] passwordController];
5041 __block BOOL shown = NO;
5042 [passwordController findAndFillPasswordForms:username
5043 password:password
5044 completionHandler:^(BOOL completed) {
5045 if (shown || !completed || ![message length])
5046 return;
pinkerton07e27842017-03-02 15:29:025047 TriggerHapticFeedbackForNotification(
5048 UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:135049 [self showSnackbar:message];
5050 shown = YES;
5051 }];
5052 break;
5053 }
5054 default:
5055 break;
5056 }
5057}
5058
5059- (void)showErrorAlert:(int)titleMessageId message:(int)messageId {
5060 NSString* title = l10n_util::GetNSString(titleMessageId);
5061 NSString* message = l10n_util::GetNSString(messageId);
5062 [self showErrorAlertWithStringTitle:title message:message];
5063}
5064
5065- (void)showErrorAlertWithStringTitle:(NSString*)title
5066 message:(NSString*)message {
5067 // Dismiss current alert.
5068 [_alertCoordinator stop];
5069
stkhapuginc9eee7b2017-04-10 15:49:275070 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5071 message:message
5072 viewController:self];
sdefresnee65fd872016-12-19 13:38:135073 [_alertCoordinator start];
5074}
5075
5076- (void)showSnackbar:(NSString*)message {
5077 [_dependencyFactory showSnackbarWithMessage:message];
5078}
5079
5080#pragma mark - Show Mail Composer methods
5081
5082- (void)showMailComposer:(id)sender {
5083 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
5084 if (![MFMailComposeViewController canSendMail]) {
5085 NSString* alertTitle =
5086 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
5087 NSString* alertMessage =
5088 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
5089 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5090 return;
5091 }
stkhapuginc9eee7b2017-04-10 15:49:275092 MFMailComposeViewController* mailViewController =
5093 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135094 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
5095 [mailViewController setToRecipients:[command toRecipients]];
5096 [mailViewController setSubject:[command subject]];
5097 [mailViewController setMessageBody:[command body] isHTML:NO];
5098
5099 const base::FilePath& textFile = [command textFileToAttach];
5100 if (!textFile.empty()) {
5101 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5102 NSData* data = [NSData dataWithContentsOfFile:filename];
5103 if (data) {
5104 NSString* displayName =
5105 base::SysUTF8ToNSString(textFile.BaseName().value());
5106 [mailViewController addAttachmentData:data
5107 mimeType:@"text/plain"
5108 fileName:displayName];
5109 }
5110 }
5111
5112 [mailViewController setMailComposeDelegate:self];
5113 [self presentViewController:mailViewController animated:YES completion:nil];
5114}
5115
5116#pragma mark - MFMailComposeViewControllerDelegate methods
5117
5118- (void)mailComposeController:(MFMailComposeViewController*)controller
5119 didFinishWithResult:(MFMailComposeResult)result
5120 error:(NSError*)error {
5121 [self dismissViewControllerAnimated:YES completion:nil];
5122}
5123
5124#pragma mark - StoreKitLauncher methods
5125
5126- (void)productViewControllerDidFinish:
5127 (SKStoreProductViewController*)viewController {
5128 [self dismissViewControllerAnimated:YES completion:nil];
5129}
5130
5131- (void)openAppStore:(NSString*)appId {
5132 if (![appId length])
5133 return;
5134 NSDictionary* product =
5135 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275136 SKStoreProductViewController* storeViewController =
5137 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135138 [storeViewController setDelegate:self];
5139 [storeViewController loadProductWithParameters:product completionBlock:nil];
5140 [self presentViewController:storeViewController animated:YES completion:nil];
5141}
5142
5143#pragma mark - TabDialogDelegate methods
5144
sdefresnee65fd872016-12-19 13:38:135145- (void)cancelDialogForTab:(Tab*)tab {
5146 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5147}
5148
5149#pragma mark - FKFeedbackPromptDelegate methods
5150
5151- (void)userTappedRateApp:(UIView*)view {
5152 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275153 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135154}
5155
5156- (void)userTappedSendFeedback:(UIView*)view {
5157 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275158 _rateThisAppDialog = nil;
5159 GenericChromeCommand* command =
5160 [[GenericChromeCommand alloc] initWithTag:IDC_REPORT_AN_ISSUE];
sdefresnee65fd872016-12-19 13:38:135161 [self chromeExecuteCommand:command];
5162}
5163
5164- (void)userTappedDismiss:(UIView*)view {
5165 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275166 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135167}
5168
5169#pragma mark - VoiceSearchBarDelegate
5170
5171- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275172 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135173 [self ensureVoiceSearchControllerCreated];
5174 return _voiceSearchController->IsTextToSpeechEnabled() &&
5175 _voiceSearchController->IsTextToSpeechSupported();
5176}
5177
5178- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275179 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135180 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5181}
5182
5183#pragma mark - VoiceSearchPresenter
5184
5185- (UIView*)voiceSearchButton {
5186 return _voiceSearchButton;
5187}
5188
5189- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5190 return [self currentLogoAnimationControllerOwner];
5191}
5192
5193@end