blob: 6dfaffb36c032fe17b0500efa6baeb8e678b3022 [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"
sdefresnee65fd872016-12-19 13:38:13115#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
116#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
117#import "ios/chrome/browser/ui/fullscreen_controller.h"
118#import "ios/chrome/browser/ui/history/tab_history_cell.h"
119#import "ios/chrome/browser/ui/key_commands_provider.h"
sdefresnee65fd872016-12-19 13:38:13120#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
121#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_panel_view_controller.h"
122#include "ios/chrome/browser/ui/omnibox/page_info_model.h"
123#import "ios/chrome/browser/ui/omnibox/page_info_view_controller.h"
124#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
125#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37126#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13127#import "ios/chrome/browser/ui/preload_controller.h"
128#import "ios/chrome/browser/ui/preload_controller_delegate.h"
129#import "ios/chrome/browser/ui/print/print_controller.h"
130#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
131#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03132#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13133#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13134#include "ios/chrome/browser/ui/rtl_geometry.h"
135#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
136#import "ios/chrome/browser/ui/stack_view/card_view.h"
137#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
138#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
139#import "ios/chrome/browser/ui/sync/sync_util.h"
140#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
141#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
142#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
143#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
144#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sdefresnee65fd872016-12-19 13:38:13145#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
146#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
147#include "ios/chrome/browser/ui/ui_util.h"
148#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28149#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13150#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
151#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51152#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13153#import "ios/chrome/browser/web/error_page_content.h"
154#import "ios/chrome/browser/web/passkit_dialog_provider.h"
eugenebutcae3d9e62017-01-27 20:01:05155#import "ios/chrome/browser/web/repost_form_tab_helper.h"
sdefresne62a00bb2017-04-10 15:36:05156#import "ios/chrome/browser/web_state_list/web_state_list.h"
157#import "ios/chrome/browser/web_state_list/web_state_opener.h"
sdefresnee65fd872016-12-19 13:38:13158#import "ios/chrome/browser/xcallback_parameters.h"
159#import "ios/chrome/common/material_timing.h"
160#include "ios/chrome/grit/ios_chromium_strings.h"
161#include "ios/chrome/grit/ios_strings.h"
162#import "ios/net/request_tracker.h"
163#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
164#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
165#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
166#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
167#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
168#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
169#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
170#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
justincohen75011c32017-04-28 16:31:39171#import "ios/shared/chrome/browser/ui/commands/command_dispatcher.h"
sczs206ca2c2017-04-13 16:37:28172#import "ios/shared/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13173#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13174#include "ios/web/public/navigation_item.h"
175#import "ios/web/public/navigation_manager.h"
176#include "ios/web/public/referrer_util.h"
177#include "ios/web/public/ssl_status.h"
178#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39179#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13180#include "ios/web/public/web_client.h"
181#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13182#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29183#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13184#include "ios/web/public/web_state/web_state.h"
185#import "ios/web/public/web_state/web_state_delegate_bridge.h"
186#include "ios/web/public/web_thread.h"
187#import "ios/web/web_state/ui/crw_web_controller.h"
188#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17189#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13190#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
191#include "net/ssl/ssl_info.h"
192#include "net/url_request/url_request_context_getter.h"
193#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
194#include "ui/base/l10n/l10n_util.h"
195#include "ui/base/l10n/l10n_util_mac.h"
196#include "ui/base/page_transition_types.h"
197#include "url/gurl.h"
198
stkhapuginf58b10d02017-04-10 13:36:17199#if !defined(__has_feature) || !__has_feature(objc_arc)
200#error "This file requires ARC support."
201#endif
202
sdefresnee65fd872016-12-19 13:38:13203using base::UserMetricsAction;
204using bookmarks::BookmarkNode;
205
206class BrowserBookmarkModelBridge;
207class InfoBarContainerDelegateIOS;
208
209namespace ios_internal {
210NSString* const kPageInfoWillShowNotification =
211 @"kPageInfoWillShowNotification";
212NSString* const kPageInfoWillHideNotification =
213 @"kPageInfoWillHideNotification";
214NSString* const kLocationBarBecomesFirstResponderNotification =
215 @"kLocationBarBecomesFirstResponderNotification";
216NSString* const kLocationBarResignsFirstResponderNotification =
217 @"kLocationBarResignsFirstResponderNotification";
218} // namespace ios_internal
219
220namespace {
221
222typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
223 // Note: these values must match the ContextMenuOption enum in histograms.xml.
224 ACTION_OPEN_IN_NEW_TAB = 0,
225 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
226 ACTION_COPY_LINK_ADDRESS = 2,
227 ACTION_SAVE_IMAGE = 6,
228 ACTION_OPEN_IMAGE = 7,
229 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
230 ACTION_SEARCH_BY_IMAGE = 11,
231 ACTION_OPEN_JAVASCRIPT = 21,
232 ACTION_READ_LATER = 22,
233 NUM_ACTIONS = 23,
234};
235
236void Record(NSInteger action, bool is_image, bool is_link) {
237 if (is_image) {
238 if (is_link) {
239 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
240 NUM_ACTIONS);
241 } else {
242 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
243 NUM_ACTIONS);
244 }
245 } else {
246 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
247 NUM_ACTIONS);
248 }
249}
250
sdefresnee65fd872016-12-19 13:38:13251const CGFloat kVoiceSearchBarHeight = 59.0;
252
253// Dimensions to use when downsizing an image for search-by-image.
254const CGFloat kSearchByImageMaxImageArea = 90000.0;
255const CGFloat kSearchByImageMaxImageWidth = 600.0;
256const CGFloat kSearchByImageMaxImageHeight = 400.0;
257
258// The delay, in seconds, after startup before cleaning up the files received
259// from other applications that are not bookmarked nor referenced by an open or
260// recently closed tab.
261const int kExternalFilesCleanupDelaySeconds = 60;
262
263enum HeaderBehaviour {
264 // The header moves completely out of the screen.
265 Hideable = 0,
266 // This header stays on screen and doesn't overlap with the content.
267 Visible,
268 // This header stay on screen and covers part of the content.
269 Overlap
270};
271
sdefresnee65fd872016-12-19 13:38:13272const CGFloat kIPadFindBarOverlap = 11;
273
274bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24275 // Most URLs are allowed in incognito; the following is an exception.
276 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13277}
278
279// Temporary key to use when storing native controllers vended to tabs before
280// they are added to the tab model.
281NSString* const kNativeControllerTemporaryKey = @"NativeControllerTemporaryKey";
282
rohitrao005a6432017-03-16 20:52:42283} // namespace
sdefresnee65fd872016-12-19 13:38:13284
stkhapugin952ecef2017-04-11 12:11:45285#pragma mark - HeaderDefinition helper
286
287@interface HeaderDefinition : NSObject
288
289// The header view.
290@property(nonatomic, strong) UIView* view;
291// How to place the view, and its behaviour when the headers move.
292@property(nonatomic, assign) HeaderBehaviour behaviour;
293// Reduces the height of a header to adjust for shadows.
294@property(nonatomic, assign) CGFloat heightAdjustement;
295// Nudges that particular header up by this number of points.
296@property(nonatomic, assign) CGFloat inset;
297
298- (instancetype)initWithView:(UIView*)view
299 headerBehaviour:(HeaderBehaviour)behaviour
300 heightAdjustment:(CGFloat)heightAdjustment
301 inset:(CGFloat)inset;
302
303+ (instancetype)definitionWithView:(UIView*)view
304 headerBehaviour:(HeaderBehaviour)behaviour
305 heightAdjustment:(CGFloat)heightAdjustment
306 inset:(CGFloat)inset;
307
308@end
309
310@implementation HeaderDefinition
311@synthesize view = _view;
312@synthesize behaviour = _behaviour;
313@synthesize heightAdjustement = _heightAdjustement;
314@synthesize inset = _inset;
315
316+ (instancetype)definitionWithView:(UIView*)view
317 headerBehaviour:(HeaderBehaviour)behaviour
318 heightAdjustment:(CGFloat)heightAdjustment
319 inset:(CGFloat)inset {
320 return [[self alloc] initWithView:view
321 headerBehaviour:behaviour
322 heightAdjustment:heightAdjustment
323 inset:inset];
324}
325
326- (instancetype)initWithView:(UIView*)view
327 headerBehaviour:(HeaderBehaviour)behaviour
328 heightAdjustment:(CGFloat)heightAdjustment
329 inset:(CGFloat)inset {
330 self = [super init];
331 if (self) {
332 _view = view;
333 _behaviour = behaviour;
334 _heightAdjustement = heightAdjustment;
335 _inset = inset;
336 }
337 return self;
338}
339
340@end
341
342#pragma mark - BVC
343
sdefresnee65fd872016-12-19 13:38:13344@interface BrowserViewController ()<AppRatingPromptDelegate,
345 ContextualSearchControllerDelegate,
346 ContextualSearchPanelMotionObserver,
347 CRWNativeContentProvider,
348 CRWWebStateDelegate,
349 DialogPresenterDelegate,
350 FullScreenControllerDelegate,
351 KeyCommandsPlumbing,
352 MFMailComposeViewControllerDelegate,
353 NewTabPageControllerObserver,
354 OverscrollActionsControllerDelegate,
355 PassKitDialogProvider,
356 PreloadControllerDelegate,
357 ShareToDelegate,
358 SKStoreProductViewControllerDelegate,
359 SnapshotOverlayProvider,
360 StoreKitLauncher,
361 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19362 TabHeadersDelegate,
sdefresnee65fd872016-12-19 13:38:13363 TabModelObserver,
364 TabSnapshottingDelegate,
365 UIGestureRecognizerDelegate,
366 UpgradeCenterClientProtocol,
367 VoiceSearchBarDelegate,
368 VoiceSearchBarOwner> {
369 // The dependency factory passed on initialization. Used to vend objects used
370 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27371 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13372
373 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27374 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13375
376 // Facade objects used by |_toolbarController|.
377 // Must outlive |_toolbarController|.
378 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
379 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
380
381 // Preload controller. Must outlive |_toolbarController|.
stkhapuginc9eee7b2017-04-10 15:49:27382 PreloadController* _preloadController;
sdefresnee65fd872016-12-19 13:38:13383
384 // The WebToolbarController used to display the omnibox.
stkhapuginc9eee7b2017-04-10 15:49:27385 WebToolbarController* _toolbarController;
sdefresnee65fd872016-12-19 13:38:13386
387 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27388 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13389
390 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27391 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13392
393 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27394 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13395
396 // Handles presentation of JavaScript dialogs.
397 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
398
justincohen75011c32017-04-28 16:31:39399 // Handles command dispatching.
400 CommandDispatcher* _dispatcher;
401
sdefresnee65fd872016-12-19 13:38:13402 // Keyboard commands provider. It offloads most of the keyboard commands
403 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27404 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13405
406 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
407 // to |-reparentToolbarController| will reset it to NO.
408 BOOL _isToolbarControllerRelinquished;
409
410 // The controller that owns the currently relinquished toolbar controller.
411 // The reference is weak because it's possible for the toolbar owner to be
412 // deallocated mid-animation due to memory pressure or a tab being closed
413 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27414 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13415
416 // Always present on tablet; always nil on phone.
stkhapuginc9eee7b2017-04-10 15:49:27417 TabStripController* _tabStripController;
sdefresnee65fd872016-12-19 13:38:13418
419 // The contextual search controller.
stkhapuginc9eee7b2017-04-10 15:49:27420 ContextualSearchController* _contextualSearchController;
sdefresnee65fd872016-12-19 13:38:13421
422 // The contextual search panel (always a subview of |self.view| if it exists).
423 ContextualSearchPanelView* _contextualSearchPanel;
424
425 // The contextual search mask (always a subview of |self.view| if it exists).
426 ContextualSearchMaskView* _contextualSearchMask;
427
428 // Used to inject Javascript implementing the PaymentRequest API and to
429 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27430 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13431
432 // Used to display the Page Info UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27433 PageInfoViewController* _pageInfoController;
sdefresnee65fd872016-12-19 13:38:13434
435 // Used to display the Voice Search UI. Nil if not visible.
436 scoped_refptr<VoiceSearchController> _voiceSearchController;
437
438 // Used to display the QR Scanner UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27439 QRScannerViewController* _qrScannerViewController;
sdefresnee65fd872016-12-19 13:38:13440
gambard6299cc1d2017-02-21 13:06:03441 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27442 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03443
gambardd2e44fb2017-01-25 09:14:21444 // Used to display the Suggestions.
stkhapuginc9eee7b2017-04-10 15:49:27445 ContentSuggestionsCoordinator* _contentSuggestionsCoordinator;
gambardd2e44fb2017-01-25 09:14:21446
sdefresnee65fd872016-12-19 13:38:13447 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27448 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13449
sdefresnee65fd872016-12-19 13:38:13450 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27451 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13452
453 // Records the set of domains for which full screen alert has already been
454 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27455 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13456
457 // Adapter to let BVC be the delegate for WebState.
458 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
459
460 // YES if new tab is animating in.
461 BOOL _inNewTabAnimation;
462
463 // YES if Voice Search should be started when the new tab animation is
464 // finished.
465 BOOL _startVoiceSearchAfterNewTabAnimation;
466
467 // YES if the user interacts with the location bar.
468 BOOL _locationBarHasFocus;
469 // YES if a load was cancelled due to typing in the location bar.
470 BOOL _locationBarEditCancelledLoad;
471 // YES if waiting for a foreground tab due to expectNewForegroundTab.
472 BOOL _expectingForegroundTab;
473
474 // The ChromeBrowserState associated with this BVC.
475 ios::ChromeBrowserState* _browserState; // weak
476
477 // Whether or not Incognito* is enabled.
478 BOOL _isOffTheRecord;
479
480 // The last point within |_contentArea| that's received a touch.
481 CGPoint _lastTapPoint;
482
483 // The time at which |_lastTapPoint| was most recently set.
484 CFTimeInterval _lastTapTime;
485
486 // A single infobar container handles all infobars in all tabs. It keeps
487 // track of infobars for current tab (accessed via infobar helper of
488 // the current tab).
489 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
490
491 // Bridge class to deliver container change notifications to BVC.
492 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
493
494 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37495 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27496 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13497
498 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11499 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13500
501 // Card side swipe view.
stkhapuginc9eee7b2017-04-10 15:49:27502 CardSideSwipeView* _sideSwipeView;
sdefresnee65fd872016-12-19 13:38:13503
sdefresnee65fd872016-12-19 13:38:13504 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27505 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13506
507 // Bridge to register for bookmark changes.
508 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
509
510 // Cached pointer to the bookmarks model.
511 bookmarks::BookmarkModel* _bookmarkModel; // weak
512
513 // The controller that shows the bookmarking UI after the user taps the star
514 // button.
stkhapuginc9eee7b2017-04-10 15:49:27515 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13516
517 // Used to remove unreferenced external files.
518 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
519
520 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27521 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13522
523 // Maps tab IDs to the most recent native content controller vended to that
524 // tab's web controller.
stkhapuginc9eee7b2017-04-10 15:49:27525 NSMapTable* _nativeControllersForTabIDs;
sdefresnee65fd872016-12-19 13:38:13526
527 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27528 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13529
530 // The sender for the last received IDC_VOICE_SEARCH command.
stkhapuginc9eee7b2017-04-10 15:49:27531 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13532
533 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27534 AlertCoordinator* _alertCoordinator;
sdefresnee65fd872016-12-19 13:38:13535}
536
537// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17538@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13539// The browser's preload controller.
stkhapuginf58b10d02017-04-10 13:36:17540@property(nonatomic, strong, readonly) PreloadController* preloadController;
sdefresnee65fd872016-12-19 13:38:13541// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17542@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13543// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17544@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13545// Whether the current tab can enable the reader mode menu item.
546@property(nonatomic, assign, readonly) BOOL canUseReaderMode;
547// Whether the current tab can enable the request desktop menu item.
548@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
549// Whether the sharing menu should be enabled.
550@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
551// Helper method to check web controller canShowFindBar method.
552@property(nonatomic, assign, readonly) BOOL canShowFindBar;
553// Whether the controller's view is currently available.
554// YES from viewWillAppear to viewWillDisappear.
555@property(nonatomic, assign, getter=isVisible) BOOL visible;
556// Whether the controller's view is currently visible.
557// YES from viewDidAppear to viewWillDisappear.
558@property(nonatomic, assign) BOOL viewVisible;
559// Whether the controller is currently dismissing a presented view controller.
560@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
561// Returns YES if the toolbar has not been scrolled out by fullscreen.
562@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
563 BOOL toolbarOnScreen;
564// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12565@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13566// Whether BVC prefers to hide the status bar. This value is used to determine
567// the response from the |prefersStatusBarHidden| method.
568@property(nonatomic, assign) BOOL hideStatusBar;
569// Whether the VoiceSearchBar should be displayed.
570@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
571// Coordinator for displaying a modal overlay with activity indicator to prevent
572// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17573@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13574 ActivityOverlayCoordinator* activityOverlayCoordinator;
575
liaoyukeea9f3ee62017-03-07 22:05:39576// The user agent type used to load the currently visible page. User agent type
577// is NONE if there is no visible page or visible page is a native page.
578@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
579
stkhapugin952ecef2017-04-11 12:11:45580// Returns the header views, all the chrome on top of the page, including the
581// ones that cannot be scrolled off screen by full screen.
582@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
583
sdefresnee65fd872016-12-19 13:38:13584// BVC initialization:
585// If the BVC is initialized with a valid browser state & tab model immediately,
586// the path is straightforward: functionality is enabled, and the UI is built
587// when -viewDidLoad is called.
588// If the BVC is initialized without a browser state or tab model, the tab model
589// and browser state may or may not be provided before -viewDidLoad is called.
590// In most cases, they will not, to improve startup performance.
591// In order to handle this, initialization of various aspects of BVC have been
592// broken out into the following functions, which have expectations (enforced
593// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
594
595// Registers for notifications.
596- (void)registerForNotifications;
597// Called when a tab is starting to load. If it's a link click or form
598// submission, the user is navigating away from any entries in the forward
599// history. Tell the toolbar so it can update the UI appropriately.
600// See the warning on [Tab webWillStartLoadingURL] about invocation of this
601// method sequence by malicious pages.
602- (void)pageLoadStarting:(NSNotification*)notify;
603// Called when a tab actually starts loading.
604- (void)pageLoadStarted:(NSNotification*)notify;
605// Called when a tab finishes loading. Update the Omnibox with the url and
606// stop any page load progess display.
607- (void)pageLoadComplete:(NSNotification*)notify;
608// Called when a tab is deselected in the model.
609// This notification also occurs when a tab is closed.
610- (void)tabDeselected:(NSNotification*)notify;
611// Animates sliding current tab and rotate-entering new tab while new tab loads
612// in background on the iPhone only.
613- (void)tabWasAdded:(NSNotification*)notify;
614
615// Updates non-view-related functionality with the given browser state and tab
616// model.
617// Does not matter whether or not the view has been loaded.
618- (void)updateWithTabModel:(TabModel*)model
619 browserState:(ios::ChromeBrowserState*)browserState;
620// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
621// the status bar to mimic this layout.
622- (void)installFakeStatusBar;
623// Builds the UI parts of tab strip and the toolbar. Does not matter whether
624// or not browser state and tab model are valid.
625- (void)buildToolbarAndTabStrip;
626// Updates view-related functionality with the given tab model and browser
627// state. The view must have been loaded. Uses |_browserState| and |_model|.
628- (void)addUIFunctionalityForModelAndBrowserState;
629// Sets the correct frame and heirarchy for subviews and helper views.
630- (void)setUpViewLayout;
631// Sets the correct frame for the tab strip based on the given maximum width.
632- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
633// Makes |tab| the currently visible tab, displaying its view. Calls
634// -selectedTabChanged on the toolbar only if |newSelection| is YES.
635- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
636// Initializes the bookmark interaction controller if not already initialized.
637- (void)initializeBookmarkInteractionController;
638
sdefresnee65fd872016-12-19 13:38:13639// Shows the tools menu popup.
640- (void)showToolsMenuPopup;
641// Add all delegates to the provided |tab|.
642- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14643// Remove delegates from the provided |tab|.
644- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13645// Closes the current tab, with animation if applicable.
646- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13647// Shows the menu to initiate sharing |data|.
648- (void)sharePageWithData:(ShareToData*)data;
649// Convenience method to share the current page.
650- (void)sharePage;
651// Prints the web page in the current tab.
652- (void)print;
653// Shows the Online Help Page in a tab.
654- (void)showHelpPage;
655// Show the bookmarks page.
656- (void)showAllBookmarks;
657// Shows a panel within the New Tab Page.
658- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel;
659// Shows the "rate this app" dialog.
660- (void)showRateThisAppDialog;
661// Dismisses the "rate this app" dialog.
662- (void)dismissRateThisAppDialog;
663#if !defined(NDEBUG)
664// Shows the source of the current page.
665- (void)viewSource;
666#endif
olivierrobin889af53f2017-03-01 14:56:32667// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13668- (BOOL)isTabNativePage:(Tab*)tab;
669// Returns the view to use when animating a page in or out, positioning it to
670// fill the content area but not actually adding it to the view hierarchy.
671- (UIImageView*)pageOpenCloseAnimationView;
672// Returns the view to use when animating full screen NTP paper in, filling the
673// entire screen but not actually adding it to the view hierarchy.
674- (UIImageView*)pageFullScreenOpenCloseAnimationView;
675// Updates the toolbar display based on the current tab.
676- (void)updateToolbar;
677// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12678// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13679- (void)updateDialogPresenterActiveState;
680// Dismisses popups and modal dialogs that are displayed above the BVC upon size
681// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
682// is performed.
683// TODO(crbug.com/522721): Support size changes for all popups and modal
684// dialogs.
685- (void)dismissPopups;
686// Create and show the find bar.
687- (void)initFindBarForTab;
688// Search for find bar query string.
689- (void)searchFindInPage;
690// Update find bar with model data. If |shouldFocus| is set to YES, the text
691// field will become first responder.
692- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
693// Close and disable find in page bar.
694- (void)closeFindInPage;
695// Hide find bar.
696- (void)hideFindBarWithAnimation:(BOOL)animate;
697// Shows find bar. If |selectText| is YES, all text inside the Find Bar
698// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
699// set to be first responder.
700- (void)showFindBarWithAnimation:(BOOL)animate
701 selectText:(BOOL)selectText
702 shouldFocus:(BOOL)shouldFocus;
703// Show the Page Security Info.
704- (void)showPageInfoPopupForView:(UIView*)sourceView;
705// Hide the Page Security Info.
706- (void)hidePageInfoPopupForView:(UIView*)sourceView;
707// Shows the tab history popup containing the tab's backward history.
708- (void)showTabHistoryPopupForBackwardHistory;
709// Shows the tab history popup containing the tab's forward history.
710- (void)showTabHistoryPopupForForwardHistory;
711// Navigate back/forward to the selected entry in the tab's history.
712- (void)navigateToSelectedEntry:(id)sender;
713// The infobar state (typically height) has changed.
714- (void)infoBarContainerStateChanged:(bool)is_animating;
715// Adds a CardView on top of the contentArea either taking the size of the full
716// screen or just the size of the space under the header.
717// Returns the CardView that was added.
718- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
719// Called when either a tab finishes loading or when a tab with finished content
720// is added directly to the model via pre-rendering. The tab must be non-nil and
721// must be a member of the tab model controlled by this BrowserViewController.
722- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
723// Evaluates Javascript asynchronously using the current page context.
724- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13725// Helper methods used by ShareToDelegate methods.
726// Shows an alert with the given title and message id.
727- (void)showErrorAlert:(int)titleMessageId message:(int)messageId;
728// Helper method displaying an alert with the given title and message.
729// Dismisses previous alert if it has not been dismissed yet.
730- (void)showErrorAlertWithStringTitle:(NSString*)title
731 message:(NSString*)message;
732// Shows a self-dismissing snackbar displaying |message|.
733- (void)showSnackbar:(NSString*)message;
734// Induces an intentional crash in the browser process.
735- (void)induceBrowserCrash;
736// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17737- (void)managePermissionAndSaveImage:(NSData*)data
738 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13739// Saves the image. In order to keep the metadata of the image, the image is
740// saved as a temporary file on disk then saved in photos.
741// This should be called on FILE thread.
gambard9efce7a2017-02-09 18:53:17742- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13743// Called when Chrome has been denied access to the photos or videos and the
744// user can change it.
745// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
746// settings. Dismiss previous alert if it has not been dismissed yet.
747- (void)displayImageErrorAlertWithSettingsOnMainQueue;
748// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
749// previous alert if it has not been dismissed yet.
750- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
751// Called when Chrome has been denied access to the photos or videos and the
752// user cannot change it.
753// Shows a privacy alert on the main queue, with errorContent as the message.
754// Dismisses previous alert if it has not been dismissed yet.
755- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
756// Called with the results of saving a picture in the photo album. If error is
757// nil the save succeeded.
758- (void)finishSavingImageWithError:(NSError*)error;
759// Provides a view that encompasses currently displayed infobar(s) or nil
760// if no infobar is presented.
761- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
762// Returns a vertical infobar offset relative to the tab content.
763- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
764// Provides a view that encompasses the voice search bar if it's displayed or
765// nil if the voice search bar isn't displayed.
766- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
767// Returns a vertical voice search bar offset relative to the tab content.
768- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
769// Lazily instantiates |_voiceSearchController|.
770- (void)ensureVoiceSearchControllerCreated;
771// Lazily instantiates |_voiceSearchBar| and adds it to the view.
772- (void)ensureVoiceSearchBarCreated;
773// Shows/hides the voice search bar.
774- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
775// The LogoAnimationControllerOwner to be used for the next logo transition
776// animation.
777- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13778// Returns the footer view if one exists (e.g. the voice search bar).
779- (UIView*)footerView;
780// Returns the height of the header view for the tab model's current tab.
781- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13782// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45783- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13784 atOffset:(CGFloat)headerOffset;
785// Returns the y coordinate for the footer's frame when animating the footer
786// in/out of fullscreen.
787- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
788// Called when the animation for setting the header view's offset is finished.
789// |completed| should indicate if the animation finished completely or was
790// interrupted. |offset| should indicate the header offset after the animation.
791// |dragged| should indicate if the header moved due to the user dragging.
792- (void)fullScreenController:(FullScreenController*)controller
793 headerAnimationCompleted:(BOOL)completed
794 offset:(CGFloat)offset;
795// Performs a search with the image at the given url. The referrer is used to
796// download the image.
797- (void)searchByImageAtURL:(const GURL&)url
798 referrer:(const web::Referrer)referrer;
799// Saves the image at the given URL on the system's album. The referrer is used
800// to download the image.
801- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
802
803// Determines the center of |sender| if it's a view or a toolbar item, and save
804// the CGPoint and timestamp.
805- (void)setLastTapPoint:(id)sender;
806// Get return the last stored |_lastTapPoint| if it's been set within the past
807// second.
808- (CGPoint)lastTapPoint;
809// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
810- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
811// Returns the native controller being used by |tab|'s web controller.
812- (id)nativeControllerForTab:(Tab*)tab;
813// Installs the BVC as overscroll actions controller of |nativeContent| if
814// needed. Sets the style of the overscroll actions toolbar.
815- (void)setOverScrollActionControllerToStaticNativeContent:
816 (StaticHtmlNativeContent*)nativeContent;
817// Whether the BVC should declare keyboard commands.
818- (BOOL)shouldRegisterKeyboardCommands;
819// Adds the given url to the reading list.
820- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
821@end
822
823class InfoBarContainerDelegateIOS
824 : public infobars::InfoBarContainer::Delegate {
825 public:
826 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
827 : controller_(controller) {}
828
829 ~InfoBarContainerDelegateIOS() override {}
830
831 private:
832 SkColor GetInfoBarSeparatorColor() const override {
833 NOTIMPLEMENTED();
834 return SK_ColorBLACK;
835 }
836
837 int ArrowTargetHeightForInfoBar(
838 size_t index,
839 const gfx::SlideAnimation& animation) const override {
840 return 0;
841 }
842
843 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
844 int arrow_target_height,
845 int bar_target_height,
846 int* arrow_height,
847 int* arrow_half_width,
848 int* bar_height) const override {
849 DCHECK_NE(-1, bar_target_height)
850 << "Infobars don't have a default height on iOS";
851 *arrow_height = 0;
852 *arrow_half_width = 0;
853 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
854 }
855
856 void InfoBarContainerStateChanged(bool is_animating) override {
857 [controller_ infoBarContainerStateChanged:is_animating];
858 }
859
860 bool DrawInfoBarArrows(int* x) const override { return false; }
861
stkhapuginf58b10d02017-04-10 13:36:17862 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13863};
864
865// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
866@interface BrowserViewController (BookmarkBridgeMethods)
867// If a bookmark matching the currentTab url is added or moved, update the
868// toolbar state so the star highlight is in sync.
869- (void)bookmarkNodeModified:(const BookmarkNode*)node;
870- (void)allBookmarksRemoved;
871@end
872
873// Handle notification that bookmarks has been removed changed so we can update
874// the bookmarked star icon.
875class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
876 public:
877 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
878 : owner_(owner) {}
879
880 ~BrowserBookmarkModelBridge() override {}
881
882 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
883 const BookmarkNode* parent,
884 int old_index,
885 const BookmarkNode* node,
886 const std::set<GURL>& removed_urls) override {
887 [owner_ bookmarkNodeModified:node];
888 }
889
890 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
891 bool ids_reassigned) override {}
892
893 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
894 const BookmarkNode* old_parent,
895 int old_index,
896 const BookmarkNode* new_parent,
897 int new_index) override {}
898
899 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
900 const BookmarkNode* parent,
901 int index) override {
902 [owner_ bookmarkNodeModified:parent->GetChild(index)];
903 }
904
905 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
906 const BookmarkNode* node) override {}
907
908 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
909 const BookmarkNode* node) override {}
910
911 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
912 const BookmarkNode* node) override {}
913
914 void BookmarkAllUserNodesRemoved(
915 bookmarks::BookmarkModel* model,
916 const std::set<GURL>& removed_urls) override {
917 [owner_ allBookmarksRemoved];
918 }
919
920 private:
stkhapuginf58b10d02017-04-10 13:36:17921 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13922};
923
924@implementation BrowserViewController
925
926@synthesize contentArea = _contentArea;
927@synthesize typingShield = _typingShield;
928@synthesize active = _active;
929@synthesize visible = _visible;
930@synthesize viewVisible = _viewVisible;
931@synthesize dismissingModal = _dismissingModal;
932@synthesize hideStatusBar = _hideStatusBar;
933@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
934@synthesize presenting = _presenting;
935
936#pragma mark - Object lifecycle
937
938- (instancetype)initWithTabModel:(TabModel*)model
939 browserState:(ios::ChromeBrowserState*)browserState
940 dependencyFactory:
941 (BrowserViewControllerDependencyFactory*)factory {
942 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
943 if (self) {
944 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17945
stkhapuginc9eee7b2017-04-10 15:49:27946 _dependencyFactory = factory;
947 _nativeControllersForTabIDs = [NSMapTable strongToWeakObjectsMapTable];
948 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
949 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39950 _dispatcher = [[CommandDispatcher alloc] init];
951 [_dispatcher startDispatchingToTarget:self
952 forProtocol:@protocol(UrlLoader)];
953 [_dispatcher startDispatchingToTarget:self
954 forProtocol:@protocol(WebToolbarDelegate)];
955 [_dispatcher startDispatchingToTarget:self
956 forSelector:@selector(chromeExecuteCommand:)];
957
sdefresnee65fd872016-12-19 13:38:13958 _javaScriptDialogPresenter.reset(
959 new JavaScriptDialogPresenterImpl(_dialogPresenter));
960 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
961 // TODO(leng): Delay this.
962 [[UpgradeCenter sharedInstance] registerClient:self];
963 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13964 if (model && browserState)
965 [self updateWithTabModel:model browserState:browserState];
966 if ([[NSUserDefaults standardUserDefaults]
967 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:27968 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:13969 }
970 }
971 return self;
972}
973
974- (instancetype)initWithNibName:(NSString*)nibNameOrNil
975 bundle:(NSBundle*)nibBundleOrNil {
976 NOTREACHED();
977 return nil;
978}
979
980- (instancetype)initWithCoder:(NSCoder*)aDecoder {
981 NOTREACHED();
982 return nil;
983}
984
985- (void)dealloc {
stkhapuginc9eee7b2017-04-10 15:49:27986 _tabStripController = nil;
987 _infoBarContainer = nil;
988 _readingListMenuNotifier = nil;
sdefresnedc432f42017-01-17 14:36:59989 if (_bookmarkModel)
990 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
sdefresnee65fd872016-12-19 13:38:13991 [_model removeObserver:self];
992 [[UpgradeCenter sharedInstance] unregisterClient:self];
993 [[NSNotificationCenter defaultCenter] removeObserver:self];
994 [_toolbarController setDelegate:nil];
stkhapuginc9eee7b2017-04-10 15:49:27995 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:13996 _voiceSearchController->SetDelegate(nil);
997 [_rateThisAppDialog setDelegate:nil];
998 [_model closeAllTabs];
sdefresnee65fd872016-12-19 13:38:13999}
1000
1001#pragma mark - Accessibility
1002
1003- (BOOL)accessibilityPerformEscape {
1004 [self dismissPopups];
1005 return YES;
1006}
1007
1008#pragma mark - Properties
1009
sdefresnee65fd872016-12-19 13:38:131010- (void)setActive:(BOOL)active {
1011 if (_active == active) {
1012 return;
1013 }
1014 _active = active;
1015
1016 // If not active, display an activity indicator overlay over the view to
1017 // prevent interaction with the web page.
1018 // TODO(crbug.com/637093): This coordinator should be managed by the
1019 // coordinator used to present BrowserViewController, when implemented.
1020 if (active) {
1021 [self.activityOverlayCoordinator stop];
1022 self.activityOverlayCoordinator = nil;
1023 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171024 self.activityOverlayCoordinator =
1025 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131026 [self.activityOverlayCoordinator start];
1027 }
1028
1029 if (_browserState) {
1030 web::ActiveStateManager* active_state_manager =
1031 web::BrowserState::GetActiveStateManager(_browserState);
1032 active_state_manager->SetActive(active);
1033 }
1034
1035 [_model setWebUsageEnabled:active];
1036 [self updateDialogPresenterActiveState];
1037
1038 if (active) {
1039 // Make sure the tab (if any; it's possible to get here without a current
1040 // tab if the caller is about to create one) ends up on screen completely.
1041 Tab* currentTab = [_model currentTab];
1042 // Force loading the view in case it was not loaded yet.
1043 [self ensureViewCreated];
1044 if (_expectingForegroundTab)
1045 [currentTab.webController setOverlayPreviewMode:YES];
1046 if (currentTab)
1047 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341048 } else {
1049 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131050 }
1051 [_contextualSearchController enableContextualSearch:active];
1052 [_paymentRequestManager enablePaymentRequest:active];
1053
1054 [self setNeedsStatusBarAppearanceUpdate];
1055}
1056
1057- (void)setPrimary:(BOOL)primary {
1058 [_model setPrimary:primary];
1059 if (primary) {
1060 [self updateDialogPresenterActiveState];
1061 } else {
1062 self.dialogPresenter.active = false;
1063 }
1064}
1065
1066- (BOOL)isPlayingTTS {
1067 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1068}
1069
sdefresne6165c8742017-01-16 15:42:021070- (ios::ChromeBrowserState*)browserState {
1071 return _browserState;
1072}
1073
1074- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271075 return _model;
sdefresne6165c8742017-01-16 15:42:021076}
1077
sdefresnee65fd872016-12-19 13:38:131078- (SideSwipeController*)sideSwipeController {
1079 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271080 _sideSwipeController =
1081 [[SideSwipeController alloc] initWithTabModel:_model
1082 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131083 [_sideSwipeController setSnapshotDelegate:self];
1084 [_sideSwipeController setSwipeDelegate:self];
1085 }
1086 return _sideSwipeController;
1087}
1088
1089- (PreloadController*)preloadController {
stkhapuginc9eee7b2017-04-10 15:49:271090 return _preloadController;
sdefresnee65fd872016-12-19 13:38:131091}
1092
1093- (DialogPresenter*)dialogPresenter {
1094 return _dialogPresenter;
1095}
1096
1097- (BOOL)canUseReaderMode {
1098 Tab* tab = [_model currentTab];
1099 if ([self isTabNativePage:tab])
1100 return NO;
1101
1102 return [tab canSwitchToReaderMode];
1103}
1104
1105- (BOOL)canUseDesktopUserAgent {
1106 Tab* tab = [_model currentTab];
1107 if ([self isTabNativePage:tab])
1108 return NO;
1109
1110 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441111 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131112}
1113
1114// Whether the sharing menu should be shown.
1115- (BOOL)canShowShareMenu {
kkhorimotob110b262017-06-01 18:38:251116 const GURL& URL = [_model currentTab].lastCommittedURL;
1117 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131118}
1119
1120- (BOOL)canShowFindBar {
1121 // Make sure web controller can handle find in page.
1122 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421123 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131124 return NO;
rohitrao005a6432017-03-16 20:52:421125 }
sdefresnee65fd872016-12-19 13:38:131126
rohitrao005a6432017-03-16 20:52:421127 auto* helper = FindTabHelper::FromWebState(tab.webState);
1128 return (helper && helper->CurrentPageSupportsFindInPage() &&
1129 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131130}
1131
liaoyukeea9f3ee62017-03-07 22:05:391132- (web::UserAgentType)userAgentType {
1133 web::WebState* webState = [_model currentTab].webState;
1134 if (!webState)
1135 return web::UserAgentType::NONE;
1136 web::NavigationItem* visibleItem =
1137 webState->GetNavigationManager()->GetVisibleItem();
1138 if (!visibleItem)
1139 return web::UserAgentType::NONE;
1140
1141 return visibleItem->GetUserAgentType();
1142}
1143
sdefresnee65fd872016-12-19 13:38:131144- (void)setVisible:(BOOL)visible {
1145 if (_visible == visible)
1146 return;
1147 _visible = visible;
1148}
1149
1150- (void)setViewVisible:(BOOL)viewVisible {
1151 if (_viewVisible == viewVisible)
1152 return;
1153 _viewVisible = viewVisible;
1154 self.visible = viewVisible;
1155 [self updateDialogPresenterActiveState];
1156}
1157
1158- (BOOL)isToolbarOnScreen {
1159 return [self headerHeight] - [self currentHeaderOffset] > 0;
1160}
1161
kkhorimotoa44349c12017-04-12 23:02:121162- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1163 if (_inNewTabAnimation == inNewTabAnimation)
1164 return;
1165 _inNewTabAnimation = inNewTabAnimation;
1166 [self updateDialogPresenterActiveState];
1167}
1168
sdefresnee65fd872016-12-19 13:38:131169- (BOOL)isInNewTabAnimation {
1170 return _inNewTabAnimation;
1171}
1172
1173- (BOOL)shouldShowVoiceSearchBar {
1174 // On iPads, the voice search bar should only be shown for regular horizontal
1175 // size class configurations. It should always be shown for voice search
1176 // results Tabs on iPhones, including configurations with regular horizontal
1177 // size classes (i.e. landscape iPhone 6 Plus).
1178 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1179 UIUserInterfaceSizeClassCompact;
1180 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1181 (!IsIPadIdiom() || compactWidth);
1182}
1183
1184- (void)setHideStatusBar:(BOOL)hideStatusBar {
1185 if (_hideStatusBar == hideStatusBar)
1186 return;
1187 _hideStatusBar = hideStatusBar;
1188 [self setNeedsStatusBarAppearanceUpdate];
1189}
1190
1191#pragma mark - IBActions
1192
1193- (void)shieldWasTapped:(id)sender {
1194 [_toolbarController cancelOmniboxEdit];
1195}
1196
1197- (void)newTab:(id)sender {
1198 [self setLastTapPoint:sender];
1199 DCHECK(self.visible || self.dismissingModal);
1200 Tab* currentTab = [_model currentTab];
1201 if (currentTab) {
jif7fed8122017-02-08 13:15:251202 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
sdefresnee65fd872016-12-19 13:38:131203 }
1204 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1205 transition:ui::PAGE_TRANSITION_TYPED];
1206}
1207
1208#pragma mark - UIViewController methods
1209
1210// Perform additional set up after loading the view, typically from a nib.
1211- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281212 CGRect initialViewsRect = self.view.frame;
1213 initialViewsRect.origin.y += StatusBarHeight();
1214 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131215 UIViewAutoresizing initialViewAutoresizing =
1216 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1217
stkhapuginf58b10d02017-04-10 13:36:171218 self.contentArea =
1219 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131220 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171221 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131222 self.typingShield.autoresizingMask = initialViewAutoresizing;
1223 [self.typingShield addTarget:self
1224 action:@selector(shieldWasTapped:)
1225 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131226 self.view.autoresizingMask = initialViewAutoresizing;
1227 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1228 [self.view addSubview:self.contentArea];
1229 [self.view addSubview:self.typingShield];
1230 [super viewDidLoad];
1231
1232 // Install fake status bar for iPad iOS7
1233 [self installFakeStatusBar];
1234 [self buildToolbarAndTabStrip];
1235 [self setUpViewLayout];
1236 // If the tab model and browser state are valid, finish initialization.
1237 if (_model && _browserState)
1238 [self addUIFunctionalityForModelAndBrowserState];
1239
1240 // Add a tap gesture recognizer to save the last tap location for the source
1241 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271242 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1243 initWithTarget:self
1244 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131245 [tapRecognizer setDelegate:self];
1246 [tapRecognizer setCancelsTouchesInView:NO];
1247 [_contentArea addGestureRecognizer:tapRecognizer];
1248}
1249
1250- (void)viewDidAppear:(BOOL)animated {
1251 [super viewDidAppear:animated];
1252 self.viewVisible = YES;
1253 [self updateDialogPresenterActiveState];
1254}
1255
1256- (void)viewWillAppear:(BOOL)animated {
1257 [super viewWillAppear:animated];
1258
1259 // Reparent the toolbar if it's been relinquished.
1260 if (_isToolbarControllerRelinquished)
1261 [self reparentToolbarController];
1262
1263 self.visible = YES;
1264
1265 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251266 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131267 _infoBarContainer->RestoreInfobars();
1268 }
1269
1270 // If the controller is suspended, or has been paged out due to low memory,
1271 // updating the view will be handled when it's displayed again.
1272 if (![_model webUsageEnabled] || !self.contentArea)
1273 return;
1274 // Update the displayed tab (if any; the switcher may not have created one
1275 // yet) in case it changed while showing the switcher.
1276 Tab* currentTab = [_model currentTab];
1277 if (currentTab)
1278 [self displayTab:currentTab isNewSelection:YES];
1279}
1280
1281- (void)viewWillDisappear:(BOOL)animated {
1282 self.viewVisible = NO;
1283 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131284 [[_model currentTab] wasHidden];
1285 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251286 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131287 _infoBarContainer->SuspendInfobars();
1288 }
1289 [super viewWillDisappear:animated];
1290}
1291
1292- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1293 duration:(NSTimeInterval)duration {
1294 [super willRotateToInterfaceOrientation:orient duration:duration];
1295 [self dismissPopups];
1296 [self reshowFindBarIfNeededWithCoordinator:nil];
1297}
1298
1299- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1300 [super didRotateFromInterfaceOrientation:orient];
1301
1302 // This reinitializes the toolbar, including updating the Overlay View,
1303 // if there is one.
1304 [self updateToolbar];
1305 [self infoBarContainerStateChanged:false];
1306}
1307
1308- (BOOL)prefersStatusBarHidden {
1309 return self.hideStatusBar;
1310}
1311
1312// Called when in the foreground and the OS needs more memory. Release as much
1313// as possible.
1314- (void)didReceiveMemoryWarning {
1315 // Releases the view if it doesn't have a superview.
1316 [super didReceiveMemoryWarning];
1317
1318 // Release any cached data, images, etc that aren't in use.
1319 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1320 // but since the FaviconCache uses obj-c in the header, it can't be included
1321 // there.
1322 if (_browserState) {
1323 FaviconLoader* loader =
1324 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1325 _browserState);
1326 if (loader)
1327 loader->PurgeCache();
1328 }
1329
1330 if (![self isViewLoaded]) {
1331 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1332 // as the BrowserViewController.
1333 self.contentArea = nil;
1334 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271335 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131336 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271337 _contentSuggestionsCoordinator = nil;
1338 _qrScannerViewController = nil;
1339 _readingListCoordinator = nil;
1340 _toolbarController = nil;
1341 _toolbarModelDelegate = nil;
1342 _toolbarModelIOS = nil;
1343 _tabStripController = nil;
1344 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131345 }
1346}
1347
1348- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1349 [super traitCollectionDidChange:previousTraitCollection];
1350 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1351 // because in some cases the presented view controller isn't a child of the
1352 // BVC in the view controller hierarchy (some intervening object isn't a
1353 // view controller).
1354 [self.presentedViewController
1355 traitCollectionDidChange:previousTraitCollection];
1356 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1357 // Update voice search bar visibility.
1358 [self updateVoiceSearchBarVisibilityAnimated:NO];
1359}
1360
1361- (void)viewWillTransitionToSize:(CGSize)size
1362 withTransitionCoordinator:
1363 (id<UIViewControllerTransitionCoordinator>)coordinator {
1364 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1365 [self dismissPopups];
1366 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1367}
1368
1369- (void)reshowFindBarIfNeededWithCoordinator:
1370 (id<UIViewControllerTransitionCoordinator>)coordinator {
1371 if (![_findBarController isFindInPageShown])
1372 return;
1373
1374 // Record focused state.
1375 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1376
1377 [self hideFindBarWithAnimation:NO];
1378
stkhapuginc9eee7b2017-04-10 15:49:271379 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131380 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1381 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271382 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131383 if (strongSelf)
1384 [strongSelf showFindBarWithAnimation:NO
1385 selectText:NO
1386 shouldFocus:isFocusedBeforeReshow];
1387 };
1388
1389 BOOL enqueued =
1390 [coordinator animateAlongsideTransition:nil completion:completion];
1391 if (!enqueued) {
1392 completion(nil);
1393 }
1394}
1395
1396- (void)dismissViewControllerAnimated:(BOOL)flag
1397 completion:(void (^)())completion {
1398 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271399 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131400 [super dismissViewControllerAnimated:flag
1401 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271402 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131403 [strongSelf setDismissingModal:NO];
1404 [strongSelf setPresenting:NO];
1405 if (completion)
1406 completion();
1407 [[strongSelf dialogPresenter] tryToPresent];
1408 }];
1409}
1410
1411- (void)presentViewController:(UIViewController*)viewControllerToPresent
1412 animated:(BOOL)flag
1413 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271414 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131415 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1416 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1417 // presented, to show a temporary view of the launch screen and then remove it
1418 // when the controller for the FRE has been presented. This fix should be
1419 // removed when the FRE startup code is rewritten.
1420 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1421 experimental_flags::AlwaysDisplayFirstRun()) &&
1422 !tests_hook::DisableFirstRun();
1423 // These if statements check that |presentViewController| is being called for
1424 // the FRE case.
1425 if (firstRunLaunch &&
1426 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1427 UINavigationController* navController =
1428 base::mac::ObjCCastStrict<UINavigationController>(
1429 viewControllerToPresent);
1430 if ([navController.topViewController
1431 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1432 self.hideStatusBar = YES;
1433
1434 // Load view from Launch Screen and add it to window.
1435 NSBundle* mainBundle = base::mac::FrameworkBundle();
1436 NSArray* topObjects =
1437 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1438 UIViewController* launchScreenController =
1439 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1440 // |launchScreenView| is loaded as an autoreleased object, and is retained
1441 // by the |completion| block below.
1442 UIView* launchScreenView = launchScreenController.view;
1443 launchScreenView.userInteractionEnabled = NO;
1444 launchScreenView.frame = self.view.window.bounds;
1445 [self.view.window addSubview:launchScreenView];
1446
1447 // Replace the completion handler sent to the superclass with one which
1448 // removes |launchScreenView| and resets the status bar. If |completion|
1449 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271450 __weak BrowserViewController* weakSelf = self;
1451 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131452 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271453 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131454 if (completion)
1455 completion();
stkhapuginc9eee7b2017-04-10 15:49:271456 };
sdefresnee65fd872016-12-19 13:38:131457 }
1458 }
1459
1460 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171461 if ([_sideSwipeController inSwipe]) {
1462 [_sideSwipeController resetContentView];
1463 }
sdefresnee65fd872016-12-19 13:38:131464
1465 [super presentViewController:viewControllerToPresent
1466 animated:flag
1467 completion:finalCompletionHandler];
1468}
1469
1470#pragma mark - Notification handling
1471
1472- (void)registerForNotifications {
1473 DCHECK(_model);
1474 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1475 [defaultCenter addObserver:self
1476 selector:@selector(pageLoadStarting:)
1477 name:kTabModelTabWillStartLoadingNotification
1478 object:_model];
1479 [defaultCenter addObserver:self
1480 selector:@selector(pageLoadStarted:)
1481 name:kTabModelTabDidStartLoadingNotification
1482 object:_model];
1483 [defaultCenter addObserver:self
1484 selector:@selector(pageLoadComplete:)
1485 name:kTabModelTabDidFinishLoadingNotification
1486 object:_model];
1487 [defaultCenter addObserver:self
1488 selector:@selector(tabDeselected:)
1489 name:kTabModelTabDeselectedNotification
1490 object:_model];
1491 [defaultCenter addObserver:self
1492 selector:@selector(tabWasAdded:)
1493 name:kTabModelNewTabWillOpenNotification
1494 object:_model];
1495}
1496
1497- (void)pageLoadStarting:(NSNotification*)notify {
1498 Tab* tab = notify.userInfo[kTabModelTabKey];
1499 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511500
1501 // Stop any Find in Page searches and close the find bar when navigating to a
1502 // new page.
1503 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361504
sdefresnee65fd872016-12-19 13:38:131505 if (tab == [_model currentTab]) {
1506 // TODO(pinkerton): Fill in here about hiding the forward button on
1507 // navigation.
1508 }
1509}
1510
1511- (void)pageLoadStarted:(NSNotification*)notify {
1512 Tab* tab = notify.userInfo[kTabModelTabKey];
1513 DCHECK(tab);
1514 if (tab == [_model currentTab]) {
1515 if (![self isTabNativePage:tab]) {
1516 [_toolbarController currentPageLoadStarted];
1517 }
1518 [self updateVoiceSearchBarVisibilityAnimated:NO];
1519 }
1520}
1521
1522- (void)pageLoadComplete:(NSNotification*)notify {
1523 // Update the UI, but only if the current tab.
1524 Tab* tab = notify.userInfo[kTabModelTabKey];
1525 if (tab == [_model currentTab]) {
1526 // There isn't any need to update the toolbar here. When the page finishes,
1527 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1528 }
1529
1530 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1531
1532 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1533}
1534
1535- (void)tabDeselected:(NSNotification*)notify {
1536 DCHECK(notify);
1537 Tab* tab = notify.userInfo[kTabModelTabKey];
1538 DCHECK(tab);
1539 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221540 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131541}
1542
1543- (void)tabWasAdded:(NSNotification*)notify {
1544 Tab* tab = notify.userInfo[kTabModelTabKey];
1545 DCHECK(tab);
1546
1547 // Update map if a native controller was vended before the tab was added.
1548 id<CRWNativeContent> nativeController =
1549 [_nativeControllersForTabIDs objectForKey:kNativeControllerTemporaryKey];
1550 if (nativeController) {
1551 [_nativeControllersForTabIDs
1552 removeObjectForKey:kNativeControllerTemporaryKey];
1553 [_nativeControllersForTabIDs setObject:nativeController forKey:tab.tabId];
1554 }
1555
1556 // When adding new tabs, check what kind of reminder infobar should
1557 // be added to the new tab. Try to add only one of them.
1558 // This check is done when a new tab is added either through the Tools Menu
1559 // "New Tab" or through "New Tab" in Stack View Controller. This method
1560 // is called after a new tab has added and finished initial navigation.
1561 // If this is added earlier, the initial navigation may end up clearing
1562 // the infobar(s) that are just added. See http://crbug/340250 for details.
1563 [[UpgradeCenter sharedInstance] addInfoBarToManager:[tab infoBarManager]
1564 forTabId:[tab tabId]];
1565 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1566 ios_internal::sync::displaySyncErrors(_browserState, tab);
1567 }
1568
1569 // The rest of this function initiates the new tab animation, which is
1570 // phone-specific.
1571 if (IsIPadIdiom())
1572 return;
1573
1574 // Do nothing if browsing is currently suspended. The BVC will set everything
1575 // up correctly when browsing resumes.
1576 if (!self.visible || ![_model webUsageEnabled])
1577 return;
1578
1579 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1580
1581 // Block that starts voice search at the end of new Tab animation if
1582 // necessary.
1583 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1584 if (_startVoiceSearchAfterNewTabAnimation) {
1585 _startVoiceSearchAfterNewTabAnimation = NO;
1586 [self startVoiceSearch];
1587 }
1588 };
1589
kkhorimotoa44349c12017-04-12 23:02:121590 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131591 if (!inBackground) {
1592 UIView* animationParentView = _contentArea;
1593 // Create the new page image, and load with the new tab page snapshot.
1594 CGFloat newPageOffset = 0;
1595 UIImageView* newPage;
kkhorimotob110b262017-06-01 18:38:251596 if (tab.lastCommittedURL == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
sdefresnee65fd872016-12-19 13:38:131597 !IsIPadIdiom()) {
1598 animationParentView = self.view;
1599 newPage = [self pageFullScreenOpenCloseAnimationView];
1600 } else {
1601 newPage = [self pageOpenCloseAnimationView];
1602 }
1603 newPageOffset = newPage.frame.origin.y;
1604
1605 [tab view].frame = _contentArea.bounds;
1606 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1607 [animationParentView addSubview:newPage];
1608 CGPoint origin = [self lastTapPoint];
1609 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1610 newPage, -newPageOffset,
1611 newPage.frame.size.height - newPage.image.size.height, origin,
1612 _isOffTheRecord, NULL, ^{
1613 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121614 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421615 // Use the model's currentTab here because it is possible that it can
1616 // be reset to a new value before the new Tab animation finished (e.g.
1617 // if another Tab shows a dialog via |dialogPresenter|). However, that
1618 // tab's view hasn't been displayed yet because it was in a new tab
1619 // animation.
1620 Tab* currentTab = [_model currentTab];
1621 if (currentTab) {
1622 [self tabSelected:currentTab];
1623 }
sdefresnee65fd872016-12-19 13:38:131624 startVoiceSearchIfNecessaryBlock();
1625 });
1626 } else {
1627 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1628 // snapshot before adding the views needed for the background animation.
1629 Tab* topTab = [_model currentTab];
1630 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1631 visibleFrameOnly:self.isToolbarOnScreen];
1632 // Add three layers in order on top of the contentArea for the animation:
1633 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271634 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131635 InstallBackgroundInView(background);
1636 [_contentArea addSubview:background];
1637
1638 // 2. A CardView displaying the data from the current tab.
1639 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1640 NSString* title = [topTab title];
1641 if (![title length])
1642 title = [topTab urlDisplayString];
1643 [topCard setTitle:title];
1644 [topCard setFavicon:[topTab favicon]];
1645 [topCard setImage:image];
1646
1647 // 3. A new, blank CardView to represent the new tab being added.
1648 // Launch the new background tab animation.
1649 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1650 topCard, [_contentArea frame], IsPortrait(), ^{
1651 [background removeFromSuperview];
1652 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121653 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131654 // Resnapshot the top card if it has its own toolbar, as the toolbar
1655 // will be captured in the new tab animation, but isn't desired for
1656 // the stack view snapshots.
1657 id nativeController = [self nativeControllerForTab:topTab];
1658 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1659 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1660 startVoiceSearchIfNecessaryBlock();
1661 });
1662 }
1663}
1664
1665#pragma mark - UI Configuration and Layout
1666
1667- (void)updateWithTabModel:(TabModel*)model
1668 browserState:(ios::ChromeBrowserState*)browserState {
1669 DCHECK(model);
1670 DCHECK(browserState);
1671 DCHECK(!_model);
1672 DCHECK(!_browserState);
1673 _browserState = browserState;
1674 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271675 _model = model;
sdefresnee65fd872016-12-19 13:38:131676 [_model addObserver:self];
1677
1678 if (!_isOffTheRecord) {
1679 [DefaultIOSWebViewFactory
1680 registerWebViewFactory:[ChromeWebViewFactory class]];
1681 }
1682 NSUInteger count = [_model count];
1683 for (NSUInteger index = 0; index < count; ++index)
1684 [self installDelegatesForTab:[_model tabAtIndex:index]];
1685
1686 [self registerForNotifications];
1687
gambardbdc07cc2017-02-03 16:43:111688 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
1689 _browserState->GetRequestContext(), web::WebThread::GetBlockingPool());
stkhapuginc9eee7b2017-04-10 15:49:271690 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131691
sdefresnedc432f42017-01-17 14:36:591692 // Register for bookmark changed notification (BookmarkModel may be null
1693 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131694 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591695 if (_bookmarkModel) {
1696 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1697 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1698 }
sdefresnee65fd872016-12-19 13:38:131699}
1700
1701- (void)ensureViewCreated {
1702 ignore_result([self view]);
1703}
1704
1705- (void)browserStateDestroyed {
1706 [self setActive:NO];
1707 // Reset the toolbar opacity in case it was changed for contextual search.
1708 [self updateToolbarControlsAlpha:1.0];
1709 [self updateToolbarBackgroundAlpha:1.0];
1710 [_contextualSearchController close];
stkhapuginc9eee7b2017-04-10 15:49:271711 _contextualSearchController = nil;
sdefresnee65fd872016-12-19 13:38:131712 [_contextualSearchPanel removeFromSuperview];
1713 [_contextualSearchMask removeFromSuperview];
1714 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271715 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131716 [_toolbarController browserStateDestroyed];
1717 [_model browserStateDestroyed];
michaeldobc2f42e2017-01-12 19:04:471718 [_preloadController browserStateDestroyed];
stkhapuginc9eee7b2017-04-10 15:49:271719 _preloadController = nil;
sdefresnee65fd872016-12-19 13:38:131720 // The file remover needs the browser state, so needs to be destroyed now.
stkhapuginc9eee7b2017-04-10 15:49:271721 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131722 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391723 [_dispatcher stopDispatchingToTarget:self];
1724 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131725}
1726
1727- (void)installFakeStatusBar {
1728 if (IsIPadIdiom()) {
1729 CGFloat statusBarHeight = StatusBarHeight();
1730 CGRect statusBarFrame =
1731 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271732 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131733 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1734 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1735 [statusBarView layer].zPosition = 99;
1736 [[self view] addSubview:statusBarView];
1737 }
1738
1739 // Add a white bar on phone so that the status bar on the NTP is white.
1740 if (!IsIPadIdiom()) {
1741 CGFloat statusBarHeight = StatusBarHeight();
1742 CGRect statusBarFrame =
1743 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271744 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131745 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1746 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1747 [self.view insertSubview:statusBarView atIndex:0];
1748 }
1749}
1750
1751// Create the UI elements. May or may not have valid browser state & tab model.
1752- (void)buildToolbarAndTabStrip {
1753 DCHECK([self isViewLoaded]);
1754 DCHECK(!_toolbarModelDelegate);
1755
1756 // Create the preload controller before the toolbar controller.
1757 if (!_preloadController) {
stkhapuginc9eee7b2017-04-10 15:49:271758 _preloadController = [_dependencyFactory newPreloadController];
sdefresnee65fd872016-12-19 13:38:131759 [_preloadController setDelegate:self];
1760 }
1761
1762 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201763 _toolbarModelDelegate.reset(
1764 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131765 _toolbarModelIOS.reset([_dependencyFactory
1766 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
stkhapuginc9eee7b2017-04-10 15:49:271767 _toolbarController = [_dependencyFactory
sdefresnee65fd872016-12-19 13:38:131768 newWebToolbarControllerWithDelegate:self
1769 urlLoader:self
stkhapuginc9eee7b2017-04-10 15:49:271770 preloadProvider:_preloadController];
justincohen75011c32017-04-28 16:31:391771 [_dispatcher startDispatchingToTarget:_toolbarController
1772 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131773 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271774 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131775 _voiceSearchController->SetDelegate(_toolbarController);
1776
1777 // If needed, create the tabstrip.
1778 if (IsIPadIdiom()) {
stkhapuginc9eee7b2017-04-10 15:49:271779 _tabStripController =
1780 [_dependencyFactory newTabStripControllerWithTabModel:_model];
1781 _tabStripController.fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131782 }
1783
1784 // Create infobar container.
1785 if (!_infoBarContainerDelegate) {
1786 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1787 _infoBarContainer.reset(
1788 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1789 }
1790}
1791
1792// Enable functionality that only makes sense if the views are loaded and
1793// both browser state and tab model are valid.
1794- (void)addUIFunctionalityForModelAndBrowserState {
1795 DCHECK(_browserState);
1796 DCHECK(_model);
1797 DCHECK([self isViewLoaded]);
1798
1799 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1800
1801 infobars::InfoBarManager* infoBarManager =
1802 [[_model currentTab] infoBarManager];
1803 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1804
1805 // Create contextual search views and controller.
1806 if ([TouchToSearchPermissionsMediator isTouchToSearchAvailableOnDevice] &&
1807 !_browserState->IsOffTheRecord()) {
stkhapuginf58b10d02017-04-10 13:36:171808 _contextualSearchMask = [[ContextualSearchMaskView alloc] init];
sdefresnee65fd872016-12-19 13:38:131809 [self.view insertSubview:_contextualSearchMask
1810 belowSubview:[_toolbarController view]];
1811 _contextualSearchPanel = [self createPanelView];
1812 [self.view insertSubview:_contextualSearchPanel
1813 aboveSubview:[_toolbarController view]];
stkhapuginc9eee7b2017-04-10 15:49:271814 _contextualSearchController =
1815 [[ContextualSearchController alloc] initWithBrowserState:_browserState
1816 delegate:self];
sdefresnee65fd872016-12-19 13:38:131817 [_contextualSearchController setPanel:_contextualSearchPanel];
1818 [_contextualSearchController setTab:[_model currentTab]];
1819 }
1820
1821 if (experimental_flags::IsPaymentRequestEnabled()) {
stkhapuginc9eee7b2017-04-10 15:49:271822 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131823 initWithBaseViewController:self
stkhapuginc9eee7b2017-04-10 15:49:271824 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131825 [_paymentRequestManager setWebState:[_model currentTab].webState];
1826 }
1827}
1828
1829// Set the frame for the various views. View must be loaded.
1830- (void)setUpViewLayout {
1831 DCHECK([self isViewLoaded]);
1832
1833 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1834
1835 CGFloat minY = [self headerOffset];
1836
1837 // If needed, position the tabstrip.
1838 if (IsIPadIdiom()) {
1839 [self layoutTabStripForWidth:widthOfView];
1840 [[self view] addSubview:[_tabStripController view]];
1841 minY += CGRectGetHeight([[_tabStripController view] frame]);
1842 }
1843
1844 // Position the toolbar next, either at the top of the browser view or
1845 // directly under the tabstrip.
1846 CGRect toolbarFrame = [[_toolbarController view] frame];
1847 toolbarFrame.origin = CGPointMake(0, minY);
1848 toolbarFrame.size.width = widthOfView;
1849 [[_toolbarController view] setFrame:toolbarFrame];
1850
1851 // Place the infobar container above the content area.
1852 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1853 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1854
1855 // Place the toolbar controller above the infobar container.
1856 [[self view] insertSubview:[_toolbarController view]
1857 aboveSubview:infoBarContainerView];
1858 minY += CGRectGetHeight(toolbarFrame);
1859
1860 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1861 // content slightly.
1862 minY -= [ToolbarController toolbarDropShadowHeight];
1863
1864 // Adjust the content area to be under the toolbar, for fullscreen or below
1865 // the toolbar is not fullscreen.
1866 CGRect contentFrame = [_contentArea frame];
1867 CGFloat marginWithHeader = StatusBarHeight();
1868 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1869 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1870 contentFrame.origin.y = overlap;
1871 [_contentArea setFrame:contentFrame];
1872
1873 // Adjust the infobar container to be either at the bottom of the screen
1874 // (iPhone) or on the lower toolbar edge (iPad).
1875 CGRect infoBarFrame = contentFrame;
1876 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1877 infoBarFrame.size.height = 0;
1878 [infoBarContainerView setFrame:infoBarFrame];
1879
1880 // Attach the typing shield to the content area but have it hidden.
1881 [_typingShield setFrame:[_contentArea frame]];
1882 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1883 [_typingShield setHidden:YES];
1884 _typingShield.accessibilityIdentifier = @"Typing Shield";
1885 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1886}
1887
1888- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1889 UIView* tabStripView = [_tabStripController view];
1890 CGRect tabStripFrame = [tabStripView frame];
1891 tabStripFrame.origin = CGPointZero;
1892 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1893 // because the CGPointZero above will break reset the offset, but it's not
1894 // clear what removing that will do.
1895 tabStripFrame.origin.y = [self headerOffset];
1896 tabStripFrame.size.width = maxWidth;
1897 [tabStripView setFrame:tabStripFrame];
1898}
1899
1900- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1901 DCHECK(tab);
1902 // Ensure that self.view is loaded to avoid errors that can otherwise occur
1903 // when accessing |_contentArea| below.
1904 if (!_contentArea)
1905 [self ensureViewCreated];
1906
1907 DCHECK(_contentArea);
kkhorimotoa44349c12017-04-12 23:02:121908 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:131909 // Hide findbar. |updateToolbar| will restore the findbar later.
1910 [self hideFindBarWithAnimation:NO];
1911
1912 // Make new content visible, resizing it first as the orientation may
1913 // have changed from the last time it was displayed.
1914 [[tab view] setFrame:_contentArea.bounds];
1915 [_contentArea displayContentView:[tab view]];
1916 }
1917 [self updateToolbar];
1918
1919 if (newSelection)
1920 [_toolbarController selectedTabChanged];
1921
1922 // Notify the Tab that it was displayed.
1923 [tab wasShown];
1924}
1925
1926- (void)initializeBookmarkInteractionController {
1927 if (_bookmarkInteractionController)
1928 return;
stkhapuginc9eee7b2017-04-10 15:49:271929 _bookmarkInteractionController =
1930 [[BookmarkInteractionController alloc] initWithBrowserState:_browserState
1931 loader:self
1932 parentController:self];
sdefresnee65fd872016-12-19 13:38:131933}
1934
1935// Update the state of back and forward buttons, hiding the forward button if
1936// there is nowhere to go. Assumes the model's current tab is up to date.
1937- (void)updateToolbar {
1938 // If the BVC has been partially torn down for low memory, wait for the
1939 // view rebuild to handle toolbar updates.
1940 if (!(_toolbarModelIOS && _browserState))
1941 return;
1942
1943 Tab* tab = [_model currentTab];
1944 if (![tab navigationManager])
1945 return;
1946 [_toolbarController updateToolbarState];
1947 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1948
1949 if (tab.isPrerenderTab && !_toolbarModelIOS->IsLoading())
1950 [_toolbarController showPrerenderingAnimation];
1951
1952 // Also update the loading state for the tools menu (that is really an
1953 // extension of the toolbar on the iPhone).
1954 if (!IsIPadIdiom())
1955 [[_toolbarController toolsPopupController]
1956 setIsTabLoading:_toolbarModelIOS->IsLoading()];
1957
rohitrao005a6432017-03-16 20:52:421958 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
1959 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:131960 [self showFindBarWithAnimation:NO
1961 selectText:YES
1962 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:361963 }
sdefresnee65fd872016-12-19 13:38:131964
1965 // Hide the toolbar if displaying phone NTP.
1966 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:551967 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:131968 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:551969 if (item) {
1970 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:131971 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
1972 hideToolbar = isNTP && !_isOffTheRecord &&
1973 ![_toolbarController isOmniboxFirstResponder] &&
1974 ![_toolbarController showingOmniboxPopup];
1975 }
1976 [[_toolbarController view] setHidden:hideToolbar];
1977 }
1978}
1979
1980- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:121981 self.dialogPresenter.active =
1982 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:131983}
1984
1985- (void)dismissPopups {
jif7fed8122017-02-08 13:15:251986 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:131987 [self hidePageInfoPopupForView:nil];
1988 [_toolbarController dismissTabHistoryPopup];
sdefresnee65fd872016-12-19 13:38:131989}
1990
1991#pragma mark - Tap handling
1992
1993- (void)setLastTapPoint:(id)sender {
1994 CGPoint center;
1995 UIView* parentView = nil;
1996 if ([sender isKindOfClass:[UIView class]]) {
1997 center = [sender center];
1998 parentView = [sender superview];
1999 }
2000 if ([sender isKindOfClass:[ToolsMenuViewItem class]]) {
2001 parentView = [[sender tableViewCell] superview];
2002 center = [[sender tableViewCell] center];
2003 }
2004
2005 if (parentView) {
2006 _lastTapPoint = [parentView convertPoint:center toView:self.view];
2007 _lastTapTime = CACurrentMediaTime();
2008 }
2009}
2010
2011- (CGPoint)lastTapPoint {
2012 if (CACurrentMediaTime() - _lastTapTime < 1) {
2013 return _lastTapPoint;
2014 }
2015 return CGPointZero;
2016}
2017
2018- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2019 UIView* view = gestureRecognizer.view;
2020 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2021 _lastTapPoint =
2022 [[view superview] convertPoint:viewCoordinate toView:self.view];
2023 _lastTapTime = CACurrentMediaTime();
2024}
2025
2026- (BOOL)addTabIfNoTabWithNormalBrowserState {
2027 if (![_model count]) {
2028 if (!_isOffTheRecord) {
2029 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2030 transition:ui::PAGE_TRANSITION_TYPED];
2031 return YES;
2032 }
2033 }
2034 return NO;
2035}
2036
2037#pragma mark - Tab creation and selection
2038
2039// Called when either a tab finishes loading or when a tab with finished content
2040// is added directly to the model via pre-rendering.
2041- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2042 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2043
2044 // Persist the session on a delay.
2045 [_model saveSessionImmediately:NO];
2046}
2047
2048- (Tab*)addSelectedTabWithURL:(const GURL&)url
2049 postData:(TemplateURLRef::PostContent*)postData
2050 transition:(ui::PageTransition)transition {
2051 return [self addSelectedTabWithURL:url
2052 postData:postData
2053 atIndex:[_model count]
2054 transition:transition];
2055}
2056
2057- (Tab*)addSelectedTabWithURL:(const GURL&)url
2058 transition:(ui::PageTransition)transition {
2059 return [self addSelectedTabWithURL:url
2060 atIndex:[_model count]
2061 transition:transition];
2062}
2063
2064- (Tab*)addSelectedTabWithURL:(const GURL&)url
2065 atIndex:(NSUInteger)position
2066 transition:(ui::PageTransition)transition {
2067 return [self addSelectedTabWithURL:url
2068 postData:NULL
2069 atIndex:position
2070 transition:transition];
2071}
2072
2073- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2074 postData:(TemplateURLRef::PostContent*)postData
2075 atIndex:(NSUInteger)position
2076 transition:(ui::PageTransition)transition {
2077 if (position == NSNotFound)
2078 position = [_model count];
2079 DCHECK(position <= [_model count]);
2080
2081 web::NavigationManager::WebLoadParams params(URL);
2082 params.transition_type = transition;
2083 if (postData) {
2084 // Extract the content type and post params from |postData| and add them
2085 // to the load params.
2086 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2087 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2088 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172089 params.post_data.reset(data);
2090 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132091 }
sdefresnea6395912017-03-01 01:14:352092 Tab* tab = [_model insertTabWithLoadParams:params
2093 opener:nil
2094 openedByDOM:NO
2095 atIndex:position
2096 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132097 return tab;
2098}
2099
olivierrobin889af53f2017-03-01 14:56:322100// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132101- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322102 web::WebState* webState = tab.webState;
2103 if (!webState)
2104 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392105 web::NavigationItem* visibleItem =
2106 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322107 if (!visibleItem)
2108 return NO;
2109 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132110}
2111
2112- (void)expectNewForegroundTab {
2113 _expectingForegroundTab = YES;
2114}
2115
2116- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2117 CGRect viewBounds, remainder;
2118 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2119 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172120 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132121}
2122
2123- (UIImageView*)pageOpenCloseAnimationView {
2124 CGRect frame = [_contentArea bounds];
2125
2126 frame.size.height = frame.size.height - [self headerHeight];
2127 frame.origin.y = [self headerHeight];
2128
stkhapuginf58b10d02017-04-10 13:36:172129 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132130 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2131 pageView.center = center;
2132
2133 pageView.backgroundColor = [UIColor whiteColor];
2134 return pageView;
2135}
2136
2137- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142138 // Unregistration happens when the Tab is removed from the TabModel.
sdefresnee65fd872016-12-19 13:38:132139 tab.dialogDelegate = self;
2140 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132141 tab.passKitDialogProvider = self;
2142 tab.fullScreenControllerDelegate = self;
2143 if (!IsIPadIdiom()) {
2144 tab.overscrollActionsControllerDelegate = self;
2145 }
olivierrobin9ce77b82017-01-12 17:29:192146 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132147 tab.tabSnapshottingDelegate = self;
2148 // Install the proper CRWWebController delegates.
2149 tab.webController.nativeProvider = self;
2150 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512151 // BrowserViewController presents SKStoreKitViewController on behalf of a
2152 // tab.
2153 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2154 if (tabHelper)
2155 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132156 tab.webState->SetDelegate(_webStateDelegate.get());
2157}
2158
sdefresne49cf2862017-03-15 13:46:142159- (void)uninstallDelegatesForTab:(Tab*)tab {
2160 tab.dialogDelegate = nil;
2161 tab.snapshotOverlayProvider = nil;
2162 tab.passKitDialogProvider = nil;
2163 tab.fullScreenControllerDelegate = nil;
2164 if (!IsIPadIdiom()) {
2165 tab.overscrollActionsControllerDelegate = nil;
2166 }
2167 tab.tabHeadersDelegate = nil;
2168 tab.tabSnapshottingDelegate = nil;
2169 tab.webController.nativeProvider = nil;
2170 tab.webController.swipeRecognizerProvider = nil;
2171 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2172 if (tabHelper)
2173 tabHelper->SetLauncher(nil);
2174 tab.webState->SetDelegate(nullptr);
2175}
2176
sdefresnee65fd872016-12-19 13:38:132177// Called when a tab is selected in the model. Make any required view changes.
2178// The notification will not be sent when the tab is already the selected tab.
2179- (void)tabSelected:(Tab*)tab {
2180 DCHECK(tab);
2181
2182 // Ignore changes while the tab stack view is visible (or while suspended).
2183 // The display will be refreshed when this view becomes active again.
2184 if (!self.visible || ![_model webUsageEnabled])
2185 return;
2186
2187 [self displayTab:tab isNewSelection:YES];
2188
kkhorimotoa44349c12017-04-12 23:02:122189 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132190 // Now that the new tab has been displayed, return to normal. Rather than
2191 // keep a reference to the previous tab, just turn off preview mode for all
2192 // tabs (since doing so is a no-op for the tabs that don't have it set).
2193 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272194 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132195 [tab.webController setOverlayPreviewMode:NO];
2196 }
2197 }
2198}
2199
2200#pragma mark - External files
2201
2202- (NSSet*)referencedExternalFiles {
2203 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2204
2205 // TODO(noyau): this is incorrect, the caller should know that the model is
2206 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592207 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132208 return filesReferencedByTabs;
2209
2210 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2211 _bookmarkModel->GetBookmarks(&bookmarks);
2212 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2213 for (const auto& bookmark : bookmarks) {
2214 GURL bookmarkUrl = bookmark.url;
2215 if (UrlIsExternalFileReference(bookmarkUrl)) {
2216 [bookmarkedFiles
2217 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2218 }
2219 }
2220 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2221}
2222
2223- (void)removeExternalFilesImmediately:(BOOL)immediately
2224 completionHandler:(ProceduralBlock)completionHandler {
2225 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2226 DCHECK(!_isOffTheRecord);
2227 _externalFileRemover.reset(new ExternalFileRemover(self));
2228 // Delay the cleanup of the unreferenced files received from other apps
2229 // to not impact startup performance.
2230 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2231 _externalFileRemover->RemoveAfterDelay(
2232 base::TimeDelta::FromSeconds(delay),
stkhapuginf58b10d02017-04-10 13:36:172233 base::BindBlockArc(completionHandler ? completionHandler
2234 : ^{
2235 }));
sdefresnee65fd872016-12-19 13:38:132236}
2237
2238#pragma mark - SnapshotOverlayProvider methods
2239
2240- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2241 NSMutableArray* overlays = [NSMutableArray array];
2242 if (![_model webUsageEnabled]) {
2243 return overlays;
2244 }
2245 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2246 if (voiceSearchView) {
2247 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272248 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132249 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272250 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132251 [overlays addObject:voiceSearchOverlay];
2252 }
2253 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2254 if (infoBarView) {
2255 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272256 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132257 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272258 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132259 [overlays addObject:infoBarOverlay];
2260 }
2261 return overlays;
2262}
2263
2264#pragma mark -
2265
2266- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2267 if (IsIPadIdiom()) {
2268 // Not using overlays on iPad because the content is pushed down by
2269 // infobar and the transition between snapshot and fresh page can
2270 // cause both snapshot and real infobars to appear at the same time.
2271 return nil;
2272 }
2273 Tab* currentTab = [_model currentTab];
2274 if (tab && tab == currentTab) {
2275 infobars::InfoBarManager* infoBarManager = [currentTab infoBarManager];
2276 if (infoBarManager->infobar_count() > 0) {
2277 DCHECK(_infoBarContainer);
2278 return _infoBarContainer->view();
2279 }
2280 }
2281 return nil;
2282}
2283
2284- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272285 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132286 // There is no UI representation for non-current tabs or there is
2287 // no _infoBarContainer instantiated yet.
2288 // Return offset outside of tab.
2289 return CGRectGetMaxY(self.view.frame);
2290 } else if (IsIPadIdiom()) {
2291 // The infobars on iPad are display at the top of a tab.
2292 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2293 } else {
2294 // The infobars on iPhone are displayed at the bottom of a tab.
2295 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2296 return CGRectGetMaxY(visibleFrame) -
2297 CGRectGetHeight(_infoBarContainer->view().frame);
2298 }
2299}
2300
2301- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2302 Tab* currentTab = [_model currentTab];
2303 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2304 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2305 return _voiceSearchBar;
2306 }
2307 return nil;
2308}
2309
2310- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2311 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2312 // There is no UI representation for non-current tabs or there is
2313 // no visible voice search. Return offset outside of tab.
2314 return CGRectGetMaxY(self.view.frame);
2315 } else {
2316 // The voice search bar on iPhone is displayed at the bottom of a tab.
2317 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2318 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2319 }
2320}
2321
2322- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272323 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132324 VoiceSearchProvider* provider =
2325 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2326 if (provider) {
2327 _voiceSearchController =
2328 provider->CreateVoiceSearchController(_browserState);
2329 _voiceSearchController->SetDelegate(_toolbarController);
2330 }
2331 }
2332}
2333
2334- (void)ensureVoiceSearchBarCreated {
2335 if (_voiceSearchBar)
2336 return;
2337
2338 CGFloat width = CGRectGetWidth([[self view] bounds]);
2339 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2340 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272341 _voiceSearchBar = ios::GetChromeBrowserProvider()
2342 ->GetVoiceSearchProvider()
2343 ->BuildVoiceSearchBar(frame);
sdefresnee65fd872016-12-19 13:38:132344 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2345 [_voiceSearchBar setHidden:YES];
2346 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2347 UIViewAutoresizingFlexibleWidth];
2348 [self.view insertSubview:_voiceSearchBar
2349 belowSubview:_infoBarContainer->view()];
2350}
2351
2352- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2353 // Voice search bar exists and is shown/hidden.
2354 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272355 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132356 return;
2357
2358 // Voice search bar doesn't exist and thus is not visible.
2359 if (!_voiceSearchBar && !show)
2360 return;
2361
2362 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272363 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132364 else
stkhapuginc9eee7b2017-04-10 15:49:272365 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132366}
2367
2368- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2369 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2370 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2371 self.shouldShowVoiceSearchBar) {
2372 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2373 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272374 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132375 }
2376 id currentNativeController =
2377 [self nativeControllerForTab:self.tabModel.currentTab];
2378 Protocol* possibleOwnerProtocol =
2379 @protocol(LogoAnimationControllerOwnerOwner);
2380 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2381 [currentNativeController logoAnimationControllerOwner]) {
2382 // If the current native controller is showing a GLIF view (e.g. the NTP
2383 // when there is no doodle), use that GLIFControllerOwner.
2384 return [currentNativeController logoAnimationControllerOwner];
2385 }
2386 return nil;
2387}
2388
2389#pragma mark - PassKitDialogProvider methods
2390
2391- (void)presentPassKitDialog:(NSData*)data {
2392 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272393 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132394 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272395 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132396 if (error || !data) {
2397 if ([_model currentTab]) {
2398 infobars::InfoBarManager* infoBarManager =
2399 [[_model currentTab] infoBarManager];
2400 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2401 // NULL, replace if with DCHECK).
2402 if (infoBarManager)
2403 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2404 }
2405 } else {
2406 PKAddPassesViewController* passKitViewController =
2407 [_dependencyFactory newPassKitViewControllerForPass:pass];
2408 if (passKitViewController) {
2409 [self presentViewController:passKitViewController
2410 animated:YES
2411 completion:^{
2412 }];
2413 }
2414 }
2415}
2416
2417- (UIStatusBarStyle)preferredStatusBarStyle {
2418 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2419 : UIStatusBarStyleDefault;
2420}
2421
2422#pragma mark - CRWWebStateDelegate methods.
2423
eugenebut75a06fa72017-01-09 17:09:552424- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512425 createNewWebStateForURL:(const GURL&)URL
2426 openerURL:(const GURL&)openerURL
2427 initiatedByUser:(BOOL)initiatedByUser {
2428 // Check if requested web state is a popup and block it if necessary.
2429 if (!initiatedByUser) {
2430 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2431 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102432 // It's possible for a page to inject a popup into a window created via
2433 // window.open before its initial load is committed. Rather than relying
2434 // on the last committed or pending NavigationItem's referrer policy, just
2435 // use ReferrerPolicyDefault.
2436 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2437 // policy once referrer policies are correctly recorded in
2438 // NavigationItems.
2439 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512440 helper->HandlePopup(URL, referrer);
2441 return nil;
2442 }
2443 }
2444
2445 // Requested web state should not be blocked from opening.
2446 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2447 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2448
2449 // Tabs open by DOM are always renderer initiated.
2450 web::NavigationManager::WebLoadParams params(GURL{});
2451 params.transition_type = ui::PAGE_TRANSITION_LINK;
2452 params.is_renderer_initiated = true;
2453 Tab* childTab = [[self tabModel]
2454 insertTabWithLoadParams:params
2455 opener:currentTab
2456 openedByDOM:YES
2457 atIndex:TabModelConstants::kTabPositionAutomatically
2458 inBackground:NO];
2459 return childTab.webState;
2460}
2461
eugenebutb46b2122017-03-14 02:43:262462- (void)closeWebState:(web::WebState*)webState {
2463 // Only allow a web page to close itself if it was opened by DOM, or if there
2464 // are no navigation items.
2465 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582466 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262467
2468 if (![self tabModel])
2469 return;
2470
2471 NSUInteger index = [[self tabModel] indexOfTab:tab];
2472 if (index != NSNotFound)
2473 [[self tabModel] closeTabAtIndex:index];
2474}
2475
eugenebut275f5892017-03-09 22:20:512476- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552477 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2478 switch (params.disposition) {
2479 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2480 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2481 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352482 insertTabWithURL:params.url
2483 referrer:params.referrer
2484 transition:params.transition
2485 opener:LegacyTabHelper::GetTabForWebState(webState)
2486 openedByDOM:NO
2487 atIndex:TabModelConstants::kTabPositionAutomatically
2488 inBackground:(params.disposition ==
2489 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552490 return tab.webState;
2491 }
2492 case WindowOpenDisposition::CURRENT_TAB: {
2493 web::NavigationManager::WebLoadParams loadParams(params.url);
2494 loadParams.referrer = params.referrer;
2495 loadParams.transition_type = params.transition;
2496 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2497 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2498 return webState;
2499 }
eugenebutd0984e82017-02-22 23:47:512500 case WindowOpenDisposition::NEW_POPUP: {
2501 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352502 insertTabWithURL:params.url
2503 referrer:params.referrer
2504 transition:params.transition
2505 opener:LegacyTabHelper::GetTabForWebState(webState)
2506 openedByDOM:YES
2507 atIndex:TabModelConstants::kTabPositionAutomatically
2508 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512509 return tab.webState;
2510 }
eugenebut75a06fa72017-01-09 17:09:552511 default:
2512 NOTIMPLEMENTED();
2513 return nullptr;
2514 };
2515}
2516
sdefresnee65fd872016-12-19 13:38:132517- (BOOL)webState:(web::WebState*)webState
2518 handleContextMenu:(const web::ContextMenuParams&)params {
2519 // Prevent context menu from displaying for a tab which is no longer the
2520 // current one.
2521 if (webState != [_model currentTab].webState) {
2522 return NO;
2523 }
2524
2525 // No custom context menu if no valid url is available in |params|.
2526 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
2527 return NO;
2528 }
2529
2530 DCHECK(_browserState);
2531 DCHECK([_model currentTab]);
2532
stkhapuginc9eee7b2017-04-10 15:49:272533 _contextMenuCoordinator =
2534 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2535 params:params];
sdefresnee65fd872016-12-19 13:38:132536
2537 NSString* title = nil;
2538 ProceduralBlock action = nil;
2539
stkhapuginc9eee7b2017-04-10 15:49:272540 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132541 GURL link = params.link_url;
2542 bool isLink = link.is_valid();
2543 GURL imageUrl = params.src_url;
2544 bool isImage = imageUrl.is_valid();
kkhorimotob110b262017-06-01 18:38:252545 const GURL& committedURL = [_model currentTab].lastCommittedURL;
sdefresnee65fd872016-12-19 13:38:132546
2547 if (isLink) {
2548 if (link.SchemeIs(url::kJavaScriptScheme)) {
2549 // Open
2550 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2551 action = ^{
2552 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2553 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2554 };
2555 [_contextMenuCoordinator addItemWithTitle:title action:action];
2556 }
2557
2558 if (web::UrlHasWebScheme(link)) {
kkhorimotob110b262017-06-01 18:38:252559 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132560
sdefresnee65fd872016-12-19 13:38:132561 // Open in New Tab.
2562 title = l10n_util::GetNSStringWithFixup(
2563 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2564 action = ^{
2565 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
2566 [weakSelf webPageOrderedOpen:link
2567 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132568 inBackground:YES
2569 appendTo:kCurrentTab];
2570 };
2571 [_contextMenuCoordinator addItemWithTitle:title action:action];
2572 if (!_isOffTheRecord) {
2573 // Open in Incognito Tab.
2574 title = l10n_util::GetNSStringWithFixup(
2575 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2576 action = ^{
2577 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2578 [weakSelf webPageOrderedOpen:link
2579 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132580 inIncognito:YES
2581 inBackground:NO
2582 appendTo:kCurrentTab];
2583 };
2584 [_contextMenuCoordinator addItemWithTitle:title action:action];
2585 }
olivierrobin51d4cf42017-01-17 13:32:352586 }
gambard65d69152017-03-23 17:44:222587 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352588 NSString* innerText = params.link_text;
2589 if ([innerText length] > 0) {
2590 // Add to reading list.
2591 title = l10n_util::GetNSStringWithFixup(
2592 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2593 action = ^{
2594 Record(ACTION_READ_LATER, isImage, isLink);
2595 [weakSelf addToReadingListURL:link title:innerText];
2596 };
2597 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532598 }
sdefresnee65fd872016-12-19 13:38:132599 }
2600 // Copy Link.
2601 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2602 action = ^{
2603 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282604 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132605 };
2606 [_contextMenuCoordinator addItemWithTitle:title action:action];
2607 }
2608 if (isImage) {
kkhorimotob110b262017-06-01 18:38:252609 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132610 // Save Image.
gambard98b4ddf2017-04-18 07:14:052611 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132612 action = ^{
2613 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2614 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2615 };
2616 [_contextMenuCoordinator addItemWithTitle:title action:action];
2617 // Open Image.
2618 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2619 action = ^{
2620 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2621 [weakSelf loadURL:imageUrl
2622 referrer:referrer
2623 transition:ui::PAGE_TRANSITION_LINK
2624 rendererInitiated:YES];
2625 };
2626 [_contextMenuCoordinator addItemWithTitle:title action:action];
2627 // Open Image In New Tab.
2628 title = l10n_util::GetNSStringWithFixup(
2629 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2630 action = ^{
2631 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2632 [weakSelf webPageOrderedOpen:imageUrl
2633 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132634 inBackground:true
2635 appendTo:kCurrentTab];
2636 };
2637 [_contextMenuCoordinator addItemWithTitle:title action:action];
2638
2639 TemplateURLService* service =
2640 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102641 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132642 if (defaultURL && !defaultURL->image_url().empty() &&
2643 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2644 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2645 defaultURL->short_name());
2646 action = ^{
2647 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2648 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2649 };
2650 [_contextMenuCoordinator addItemWithTitle:title action:action];
2651 }
2652 }
2653
2654 [_contextMenuCoordinator start];
2655 return YES;
2656}
2657
eugenebutb739bdc2017-01-25 06:32:482658- (void)webState:(web::WebState*)webState
2659 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2660 // Display the action sheet with the arrow pointing at the top center of the
2661 // web contents.
sdefresne0452a9d2017-02-09 15:33:282662 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482663 UIView* view = webState->GetView();
2664 CGPoint dialogLocation =
2665 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282666 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032667 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172668 helper->PresentDialog(dialogLocation,
2669 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052670 handler(shouldContinue);
2671 }));
eugenebutb739bdc2017-01-25 06:32:482672}
2673
sdefresnee65fd872016-12-19 13:38:132674- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2675 (web::WebState*)webState {
2676 return _javaScriptDialogPresenter.get();
2677}
2678
eugenebut63232102017-01-19 16:19:402679- (void)webState:(web::WebState*)webState
2680 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2681 proposedCredential:(NSURLCredential*)proposedCredential
2682 completionHandler:(void (^)(NSString* username,
2683 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422684 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2685 if ([tab isPrerenderTab]) {
2686 [tab discardPrerender];
2687 if (handler) {
2688 handler(nil, nil);
2689 }
2690 return;
2691 }
2692
eugenebut63232102017-01-19 16:19:402693 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2694 proposedCredential:proposedCredential
2695 webState:webState
2696 completionHandler:handler];
2697}
2698
sdefresnee65fd872016-12-19 13:38:132699#pragma mark - FullScreenControllerDelegate methods
2700
2701- (CGFloat)headerOffset {
2702 if (IsIPadIdiom())
2703 return StatusBarHeight();
2704 return 0.0;
2705}
2706
stkhapugin952ecef2017-04-11 12:11:452707- (NSArray<HeaderDefinition*>*)headerViews {
2708 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132709 if (![self isViewLoaded])
2710 return results;
2711
2712 if (!IsIPadIdiom()) {
2713 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452714 [results addObject:[HeaderDefinition
2715 definitionWithView:[_toolbarController view]
2716 headerBehaviour:Hideable
2717 heightAdjustment:[ToolbarController
2718 toolbarDropShadowHeight]
2719 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132720 }
2721 } else {
2722 if ([_tabStripController view]) {
stkhapugin952ecef2017-04-11 12:11:452723 [results addObject:[HeaderDefinition
2724 definitionWithView:[_tabStripController view]
2725 headerBehaviour:Hideable
2726 heightAdjustment:0.0
2727 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132728 }
2729 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452730 [results addObject:[HeaderDefinition
2731 definitionWithView:[_toolbarController view]
2732 headerBehaviour:Hideable
2733 heightAdjustment:[ToolbarController
2734 toolbarDropShadowHeight]
2735 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132736 }
2737 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452738 [results addObject:[HeaderDefinition
2739 definitionWithView:[_findBarController view]
2740 headerBehaviour:Overlap
2741 heightAdjustment:0.0
2742 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:132743 }
2744 }
stkhapugin952ecef2017-04-11 12:11:452745 return [results copy];
sdefresnee65fd872016-12-19 13:38:132746}
2747
2748- (UIView*)footerView {
2749 return _voiceSearchBar;
2750}
2751
2752- (CGFloat)headerHeight {
2753 return [self headerHeightForTab:[_model currentTab]];
2754}
2755
2756- (CGFloat)headerHeightForTab:(Tab*)tab {
2757 id nativeController = [self nativeControllerForTab:tab];
2758 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2759 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2760 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2761 // On iPhone, don't add any header height for ToolbarOwner native
2762 // controllers when they're displaying their own toolbar.
2763 return 0;
2764 }
2765
stkhapugin952ecef2017-04-11 12:11:452766 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132767
2768 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452769 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:132770 if (header.view && header.behaviour == Hideable) {
2771 height += CGRectGetHeight([header.view frame]) -
2772 header.heightAdjustement - header.inset;
2773 }
2774 }
2775
2776 return height - StatusBarHeight();
2777}
2778
2779- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:192780 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:132781}
2782
2783- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:452784 NSArray<HeaderDefinition*>* headers = [self headerViews];
2785 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:132786 return 0.0;
2787
2788 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
2789 // API documentation.
2790 if ([[[self tabModel] currentTab] isPrerenderTab])
2791 return [self headerHeight];
2792
2793 UIView* topHeader = headers[0].view;
2794 return -(topHeader.frame.origin.y - [self headerOffset]);
2795}
2796
2797- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
2798 UIView* footer = [self footerView];
2799 CGFloat headerHeight = [self headerHeight];
2800 if (!footer || headerHeight == 0)
2801 return 0.0;
2802
2803 CGFloat footerHeight = CGRectGetHeight(footer.frame);
2804 CGFloat offset = headerOffset * footerHeight / headerHeight;
2805 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
2806}
2807
2808- (void)fullScreenController:(FullScreenController*)controller
2809 headerAnimationCompleted:(BOOL)completed
2810 offset:(CGFloat)offset {
2811 if (completed)
justincohen04c27772016-12-21 20:16:592812 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:132813}
2814
stkhapugin952ecef2017-04-11 12:11:452815- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:132816 atOffset:(CGFloat)headerOffset {
2817 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452818 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:132819 CGRect frame = [header.view frame];
2820 frame.origin.y = height - headerOffset - header.inset;
2821 [header.view setFrame:frame];
2822 if (header.behaviour != Overlap)
2823 height += CGRectGetHeight(frame);
2824 }
2825}
2826
2827- (void)fullScreenController:(FullScreenController*)fullScreenController
2828 drawHeaderViewFromOffset:(CGFloat)headerOffset
2829 animate:(BOOL)animate {
2830 if ([_sideSwipeController inSwipe])
2831 return;
2832
2833 CGRect footerFrame = CGRectZero;
2834 UIView* footer = nil;
2835 // Only animate the voice search bar if the tab is a voice search results tab.
2836 if ([_model currentTab].isVoiceSearchResultsTab) {
2837 footer = [self footerView];
2838 footerFrame = footer.frame;
2839 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2840 }
2841
stkhapugin952ecef2017-04-11 12:11:452842 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132843 void (^block)(void) = ^{
2844 [self setFramesForHeaders:headers atOffset:headerOffset];
2845 footer.frame = footerFrame;
2846 };
2847 void (^completion)(BOOL) = ^(BOOL finished) {
2848 [self fullScreenController:fullScreenController
2849 headerAnimationCompleted:finished
2850 offset:headerOffset];
2851 };
2852 if (animate) {
2853 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2854 delay:0.0
2855 options:UIViewAnimationOptionBeginFromCurrentState
2856 animations:block
2857 completion:completion];
2858 } else {
2859 block();
2860 completion(YES);
2861 }
2862}
2863
2864- (void)fullScreenController:(FullScreenController*)fullScreenController
2865 drawHeaderViewFromOffset:(CGFloat)headerOffset
2866 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
2867 changeTopContentPadding:(BOOL)changeTopContentPadding
2868 scrollingToOffset:(CGFloat)contentOffset {
2869 DCHECK(webViewProxy);
2870 if ([_sideSwipeController inSwipe])
2871 return;
2872
2873 CGRect footerFrame;
2874 UIView* footer = nil;
2875 // Only animate the voice search bar if the tab is a voice search results tab.
2876 if ([_model currentTab].isVoiceSearchResultsTab) {
2877 footer = [self footerView];
2878 footerFrame = footer.frame;
2879 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2880 }
2881
stkhapugin952ecef2017-04-11 12:11:452882 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132883 void (^block)(void) = ^{
2884 [self setFramesForHeaders:headers atOffset:headerOffset];
2885 footer.frame = footerFrame;
2886 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
2887 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
2888 if (changeTopContentPadding)
2889 webViewProxy.topContentPadding = contentOffset;
2890 };
2891 void (^completion)(BOOL) = ^(BOOL finished) {
2892 [self fullScreenController:fullScreenController
2893 headerAnimationCompleted:finished
2894 offset:headerOffset];
2895 };
2896
2897 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2898 delay:0.0
2899 options:UIViewAnimationOptionBeginFromCurrentState
2900 animations:block
2901 completion:completion];
2902}
2903
2904#pragma mark - VoiceSearchBarOwner
2905
2906- (id<VoiceSearchBar>)voiceSearchBar {
2907 return _voiceSearchBar;
2908}
2909
2910#pragma mark - Install OverScrollActionController method.
2911- (void)setOverScrollActionControllerToStaticNativeContent:
2912 (StaticHtmlNativeContent*)nativeContent {
2913 if (!IsIPadIdiom() && !FirstRun::IsChromeFirstRun()) {
2914 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:172915 [[OverscrollActionsController alloc]
2916 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:132917 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:052918 OverscrollStyle style = _isOffTheRecord
2919 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2920 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:132921 controller.style = style;
2922 nativeContent.overscrollActionsController = controller;
2923 }
2924}
2925
2926#pragma mark - OverscrollActionsControllerDelegate methods.
2927
2928- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:052929 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:132930 switch (action) {
rohitrao922b7111c2017-01-03 14:31:052931 case OverscrollAction::NEW_TAB:
sdefresnee65fd872016-12-19 13:38:132932 [self newTab:nil];
2933 break;
rohitrao922b7111c2017-01-03 14:31:052934 case OverscrollAction::CLOSE_TAB:
sdefresnee65fd872016-12-19 13:38:132935 [self closeCurrentTab];
2936 break;
liaoyuke563dc4a2017-03-17 18:36:292937 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:132938 if ([[[_model currentTab] webController] loadPhase] ==
2939 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:232940 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:132941 }
liaoyuke563dc4a2017-03-17 18:36:292942
2943 web::WebState* webState = [_model currentTab].webState;
2944 if (webState)
2945 // |check_for_repost| is true because the reload is explicitly initiated
2946 // by the user.
2947 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
2948 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:132949 break;
liaoyuke563dc4a2017-03-17 18:36:292950 }
rohitrao922b7111c2017-01-03 14:31:052951 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:132952 NOTREACHED();
2953 break;
2954 }
2955}
2956
2957- (BOOL)shouldAllowOverscrollActions {
2958 return YES;
2959}
2960
2961- (UIView*)headerView {
2962 return [_toolbarController view];
2963}
2964
2965- (UIView*)toolbarSnapshotView {
2966 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
2967}
2968
2969- (CGFloat)overscrollActionsControllerHeaderInset:
2970 (OverscrollActionsController*)controller {
2971 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
2972 return [self headerHeight];
2973 else
2974 return 0;
2975}
2976
2977- (CGFloat)overscrollHeaderHeight {
2978 return [self headerHeight] + StatusBarHeight();
2979}
2980
2981#pragma mark - TabSnapshottingDelegate methods.
2982
2983- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
2984 CGRect pageContentArea = _contentArea.bounds;
2985 if ([_model webUsageEnabled])
2986 pageContentArea = tab.view.bounds;
2987 CGFloat headerHeight = [self headerHeightForTab:tab];
2988 id nativeController = [self nativeControllerForTab:tab];
2989 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
2990 headerHeight += [nativeController toolbarHeight];
2991 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
2992 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
2993}
2994
2995#pragma mark - NewTabPageObserver methods.
2996
2997- (void)selectedPanelDidChange {
2998 [self updateToolbar];
2999}
3000
3001#pragma mark - CRWNativeContentProvider methods
3002
3003- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3004 withError:(NSError*)error
3005 isPost:(BOOL)isPost {
3006 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173007 [[ErrorPageContent alloc] initWithLoader:self
3008 browserState:self.browserState
3009 url:url
3010 error:error
3011 isPost:isPost
3012 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133013 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3014 return errorPageContent;
3015}
3016
3017- (BOOL)hasControllerForURL:(const GURL&)url {
3018 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453019 if (host == kChromeUIOfflineHost) {
3020 // Only allow offline URL that are fully specified.
3021 return reading_list::IsOfflineURLValid(
3022 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3023 }
sdefresnee65fd872016-12-19 13:38:133024
michaeldo352029b2017-05-10 20:41:383025 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost;
sdefresnee65fd872016-12-19 13:38:133026}
3027
olivierrobind43eecb2017-01-27 20:35:263028- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3029 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133030 DCHECK(url.SchemeIs(kChromeUIScheme));
3031
3032 id<CRWNativeContent> nativeController = nil;
3033 std::string url_host = url.host();
3034 if (url_host == kChromeUINewTabHost || url_host == kChromeUIBookmarksHost) {
3035 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173036 [[NewTabPageController alloc] initWithUrl:url
3037 loader:self
3038 focuser:_toolbarController
3039 ntpObserver:self
3040 browserState:_browserState
3041 colorCache:_dominantColorCache
3042 webToolbarDelegate:self
justincohenbc913632017-04-18 14:41:453043 tabModel:_model
justincohen75011c32017-04-28 16:31:393044 parentViewController:self
3045 dispatcher:_dispatcher];
sdefresnee65fd872016-12-19 13:38:133046 pageController.swipeRecognizerProvider = self.sideSwipeController;
3047
3048 // Panel is always NTP for iPhone.
3049 NewTabPage::PanelIdentifier panelType = NewTabPage::kMostVisitedPanel;
3050
3051 if (IsIPadIdiom()) {
3052 // New Tab Page can have multiple panels. Each panel is addressable
3053 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3054 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3055 // the Bookmark Manager, etc.
3056 // The utility functions NewTabPage::IdentifierFromFragment() and
3057 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3058 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3059 // without changing the URL since the URL may be chrome://bookmarks/#123.
3060 // If the URL is chrome://newtab/, pre-select the panel based on the
3061 // #fragment.
3062 panelType = url_host == kChromeUIBookmarksHost
3063 ? NewTabPage::kBookmarksPanel
3064 : NewTabPage::IdentifierFromFragment(url.ref());
3065 }
3066 [pageController selectPanel:panelType];
3067 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453068 } else if (url_host == kChromeUIOfflineHost &&
3069 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133070 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173071 [[OfflinePageNativeContent alloc] initWithLoader:self
3072 browserState:_browserState
3073 webState:webState
3074 URL:url];
sdefresnee65fd872016-12-19 13:38:133075 [self setOverScrollActionControllerToStaticNativeContent:
3076 staticNativeController];
3077 nativeController = staticNativeController;
3078 } else if (url_host == kChromeUIExternalFileHost) {
3079 // Return an instance of the |ExternalFileController| only if the file is
3080 // still in the sandbox.
3081 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3082 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173083 nativeController =
3084 [[ExternalFileController alloc] initWithURL:url
3085 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133086 }
peterlaurens44615d02017-05-23 20:23:093087 } else if (url_host == kChromeUICrashHost) {
3088 // There is no native controller for kChromeUICrashHost, it is instead
3089 // handled as any other renderer crash by the SadTabTabHelper.
3090 // nativeController must be set to nil to prevent defaulting to a
3091 // PageNotAvailableController.
3092 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133093 } else {
3094 DCHECK(![self hasControllerForURL:url]);
3095 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173096 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133097 }
3098 // If a native controller is vended before its tab is added to the tab model,
3099 // use the temporary key and add it under the new tab's tabId in the
3100 // TabModelObserver callback. This happens:
3101 // - when there is no current tab (occurs when vending the NTP controller for
3102 // the first tab that is opened),
3103 // - when the current tab's url doesn't match |url| (occurs when a native
3104 // controller is opened in a new tab)
3105 // - when the current tab's url matches |url| and there is already a native
3106 // controller of the appropriate type vended to it (occurs when a native
3107 // controller is opened in a new tab from a tab with a matching URL, e.g.
3108 // opening an NTP when an NTP is already displayed in the current tab).
3109 // For normal page loads, history navigations, tab restorations, and crash
3110 // recoveries, the tab will already exist in the tab model and the tabId can
3111 // be used as the native controller key.
3112 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3113 // that native controllers vended here always correspond to the current tab.
3114 Tab* currentTab = [_model currentTab];
3115 NSString* nativeControllerKey = currentTab.tabId;
kkhorimotob110b262017-06-01 18:38:253116 if (!currentTab || currentTab.lastCommittedURL != url ||
sdefresnee65fd872016-12-19 13:38:133117 [[_nativeControllersForTabIDs objectForKey:nativeControllerKey]
3118 isKindOfClass:[nativeController class]]) {
3119 nativeControllerKey = kNativeControllerTemporaryKey;
3120 }
3121 DCHECK(nativeControllerKey);
3122 [_nativeControllersForTabIDs setObject:nativeController
3123 forKey:nativeControllerKey];
3124 return nativeController;
3125}
3126
3127- (id)nativeControllerForTab:(Tab*)tab {
3128 id nativeController = [_nativeControllersForTabIDs objectForKey:tab.tabId];
3129 if (!nativeController) {
3130 // If there is no controller, check for a native controller stored under
3131 // the temporary key.
3132 nativeController = [_nativeControllersForTabIDs
3133 objectForKey:kNativeControllerTemporaryKey];
3134 }
3135 return nativeController;
3136}
3137
3138#pragma mark - DialogPresenterDelegate methods
3139
3140- (void)dialogPresenter:(DialogPresenter*)presenter
3141 willShowDialogForWebState:(web::WebState*)webState {
3142 for (Tab* iteratedTab in self.tabModel) {
3143 if ([iteratedTab webState] == webState) {
3144 self.tabModel.currentTab = iteratedTab;
3145 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3146 break;
3147 }
3148 }
3149}
3150
3151#pragma mark - Context menu methods
3152
3153- (void)searchByImageAtURL:(const GURL&)url
3154 referrer:(const web::Referrer)referrer {
3155 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273156 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113157 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173158 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3159 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113160 DCHECK(data);
3161 dispatch_async(dispatch_get_main_queue(), ^{
3162 [weakSelf searchByImageData:data atURL:image_source_url];
3163 });
3164 };
3165 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133166 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3167 web::PolicyForNavigation(url, referrer));
3168}
3169
3170- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3171 NSData* imageData = data;
3172 UIImage* image = [UIImage imageWithData:imageData];
3173 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3174 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3175 // kSearchByImageMaxImageHeight).
3176 if (image &&
3177 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3178 (image.size.width > kSearchByImageMaxImageWidth ||
3179 image.size.height > kSearchByImageMaxImageHeight)) {
3180 CGSize newImageSize =
3181 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3182 image = [image gtm_imageByResizingToSize:newImageSize
3183 preserveAspectRatio:YES
3184 trimToFit:NO];
3185 imageData = UIImageJPEGRepresentation(image, 1.0);
3186 }
3187
3188 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3189 std::string byteString(bytes, [imageData length]);
3190
3191 TemplateURLService* templateUrlService =
3192 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103193 const TemplateURL* defaultURL =
3194 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133195 DCHECK(!defaultURL->image_url().empty());
3196 DCHECK(defaultURL->image_url_ref().IsValid(
3197 templateUrlService->search_terms_data()));
3198 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3199 search_args.image_url = imageURL;
3200 search_args.image_thumbnail_content = byteString;
3201
3202 // Generate the URL and populate |post_content| with the content type and
3203 // HTTP body for the request.
3204 TemplateURLRef::PostContent post_content;
3205 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3206 search_args, templateUrlService->search_terms_data(), &post_content));
3207 [self addSelectedTabWithURL:result
3208 postData:&post_content
3209 transition:ui::PAGE_TRANSITION_TYPED];
3210}
3211
3212- (void)saveImageAtURL:(const GURL&)url
3213 referrer:(const web::Referrer&)referrer {
3214 DCHECK(url.is_valid());
3215
gambard9efce7a2017-02-09 18:53:173216 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3217 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113218 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133219
gambard9efce7a2017-02-09 18:53:173220 base::FilePath::StringType extension;
3221
3222 bool extensionSuccess =
3223 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3224 if (!extensionSuccess || extension.length() == 0) {
3225 extension = "png";
3226 }
3227
3228 NSString* fileExtension =
3229 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3230 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113231 };
3232 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133233 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3234 web::PolicyForNavigation(url, referrer));
3235}
3236
gambard9efce7a2017-02-09 18:53:173237- (void)managePermissionAndSaveImage:(NSData*)data
3238 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133239 switch ([PHPhotoLibrary authorizationStatus]) {
3240 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173241 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133242 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3243 // Call -saveImage again to check if chrome needs to display an error or
3244 // saves the image.
3245 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173246 [self managePermissionAndSaveImage:data
3247 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133248 }];
3249 break;
stkhapuginf58b10d02017-04-10 13:36:173250 }
sdefresnee65fd872016-12-19 13:38:133251
3252 // The application doesn't have permission to access photo and the user
3253 // cannot grant it.
3254 case PHAuthorizationStatusRestricted:
3255 [self displayPrivacyErrorAlertOnMainQueue:
3256 l10n_util::GetNSString(
3257 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3258 break;
3259
3260 // The application doesn't have permission to access photo and the user
3261 // can grant it.
3262 case PHAuthorizationStatusDenied:
3263 [self displayImageErrorAlertWithSettingsOnMainQueue];
3264 break;
3265
3266 // The application has permission to access the photos.
3267 default: {
gambard9efce7a2017-02-09 18:53:173268 web::WebThread::PostTask(
stkhapuginf58b10d02017-04-10 13:36:173269 web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
gambard9efce7a2017-02-09 18:53:173270 [self saveImage:data withFileExtension:fileExtension];
3271 }));
sdefresnee65fd872016-12-19 13:38:133272 break;
3273 }
3274 }
3275}
3276
gambard9efce7a2017-02-09 18:53:173277- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension {
gambard16cfe1f2016-12-21 13:12:093278 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
gambard9efce7a2017-02-09 18:53:173279 stringByAppendingString:fileExtension];
sdefresnee65fd872016-12-19 13:38:133280 NSURL* fileURL =
3281 [NSURL fileURLWithPath:[NSTemporaryDirectory()
3282 stringByAppendingPathComponent:fileName]];
3283 NSError* error = nil;
3284 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3285
3286 // Error while writing the image to disk.
3287 if (error) {
3288 NSString* errorMessage = [NSString
3289 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3290 [error domain], [error code]];
3291 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3292 return;
3293 }
3294
3295 // Save the image to photos.
3296 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3297 [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
3298 }
3299 completionHandler:^(BOOL success, NSError* error) {
3300 // Callback for the image saving.
3301 [self finishSavingImageWithError:error];
3302
3303 // Cleanup the temporary file.
3304 web::WebThread::PostTask(
stkhapuginf58b10d02017-04-10 13:36:173305 web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
sdefresnee65fd872016-12-19 13:38:133306 NSError* error = nil;
3307 [[NSFileManager defaultManager] removeItemAtURL:fileURL
3308 error:&error];
3309 }));
3310 }];
3311}
3312
3313- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3314 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3315 BOOL canGoToSetting =
3316 [[UIApplication sharedApplication] canOpenURL:settingURL];
3317 if (canGoToSetting) {
3318 dispatch_async(dispatch_get_main_queue(), ^{
3319 [self displayImageErrorAlertWithSettings:settingURL];
3320 });
3321 } else {
3322 [self displayPrivacyErrorAlertOnMainQueue:
3323 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3324 }
3325}
3326
3327- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3328 // Dismiss current alert.
3329 [_alertCoordinator stop];
3330
3331 NSString* title =
3332 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3333 NSString* message = l10n_util::GetNSString(
3334 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3335
stkhapuginc9eee7b2017-04-10 15:49:273336 _alertCoordinator =
3337 [[AlertCoordinator alloc] initWithBaseViewController:self
3338 title:title
3339 message:message];
sdefresnee65fd872016-12-19 13:38:133340
3341 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3342 action:nil
3343 style:UIAlertActionStyleCancel];
3344
3345 [_alertCoordinator
3346 addItemWithTitle:l10n_util::GetNSString(
3347 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3348 action:^{
3349 OpenUrlWithCompletionHandler(settingURL, nil);
3350 }
3351 style:UIAlertActionStyleDefault];
3352
3353 [_alertCoordinator start];
3354}
3355
3356- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3357 dispatch_async(dispatch_get_main_queue(), ^{
3358 NSString* title =
3359 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3360 [self showErrorAlertWithStringTitle:title message:errorContent];
3361 });
3362}
3363
3364// This callback is triggered when the image is effectively saved onto the photo
3365// album, or if the save failed for some reason.
3366- (void)finishSavingImageWithError:(NSError*)error {
3367 // Was there an error?
3368 if (error) {
3369 // Saving photo failed even though user has granted access to Photos.
3370 // Display the error information from the NSError object for user.
3371 NSString* errorMessage = [NSString
3372 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3373 [error domain], [error code]];
3374 // This code may be execute outside of the main thread. Make sure to display
3375 // the error on the main thread.
3376 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3377 } else {
3378 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3379 // the photo application. The current behaviour is to create the photo there
3380 // but not providing any link to it is suboptimal. That's what Safari is
3381 // doing, and what the PM want, but it doesn't make it right.
3382 }
3383}
3384
sdefresnee65fd872016-12-19 13:38:133385#pragma mark - Showing popups
3386
3387- (void)showToolsMenuPopup {
3388 DCHECK(_browserState);
3389 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133390
peterlaurensb04bf692017-05-19 21:06:033391 // Record the time this menu was requested; to be stored in the configuration
3392 // object.
3393 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
3394
sdefresnee65fd872016-12-19 13:38:133395 // Dismiss the omnibox (if open).
3396 [_toolbarController cancelOmniboxEdit];
3397 // Dismiss the soft keyboard (if open).
3398 [[_model currentTab].webController dismissKeyboard];
3399 // Dismiss Find in Page focus.
3400 [self updateFindBar:NO shouldFocus:NO];
3401
stkhapuginc9eee7b2017-04-10 15:49:273402 ToolsMenuConfiguration* configuration =
3403 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
peterlaurensb04bf692017-05-19 21:06:033404 configuration.requestStartTime =
3405 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
sdefresnee65fd872016-12-19 13:38:133406 if ([_model count] == 0)
liaoyukeea9f3ee62017-03-07 22:05:393407 [configuration setNoOpenedTabs:YES];
3408
sdefresnee65fd872016-12-19 13:38:133409 if (_isOffTheRecord)
liaoyukeea9f3ee62017-03-07 22:05:393410 [configuration setInIncognito:YES];
3411
gambard65d69152017-03-23 17:44:223412 if (!_readingListMenuNotifier) {
stkhapuginc9eee7b2017-04-10 15:49:273413 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
gambard65d69152017-03-23 17:44:223414 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
stkhapuginc9eee7b2017-04-10 15:49:273415 _browserState)];
sdefresnee65fd872016-12-19 13:38:133416 }
gambard65d69152017-03-23 17:44:223417 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
sdefresnee65fd872016-12-19 13:38:133418
liaoyukeea9f3ee62017-03-07 22:05:393419 [configuration setUserAgentType:self.userAgentType];
3420
3421 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
3422
sdefresnee65fd872016-12-19 13:38:133423 ToolsPopupController* toolsPopupController =
3424 [_toolbarController toolsPopupController];
3425 if ([_model currentTab]) {
3426 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
3427 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
3428 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
3429 [toolsPopupController setCanUseReaderMode:self.canUseReaderMode];
sdefresnee65fd872016-12-19 13:38:133430 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
3431
3432 if (!IsIPadIdiom())
3433 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
3434 }
3435}
3436
3437- (void)showPageInfoPopupForView:(UIView*)sourceView {
3438 Tab* tab = [_model currentTab];
3439 DCHECK([tab navigationManager]);
3440 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3441
3442 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3443 // be trigerred by a button enabled when a current item matches some
3444 // conditions. However a crash was seen were navItem was NULL hence this
3445 // test after a DCHECK.
3446 DCHECK(navItem);
3447 if (!navItem)
3448 return;
3449
olivierrobin013ba672017-03-01 21:16:243450 // Don't show if the page is native except for offline pages (to show the
3451 // offline page info).
3452 if ([self isTabNativePage:tab] &&
3453 !reading_list::IsOfflineURL(navItem->GetURL())) {
sdefresnee65fd872016-12-19 13:38:133454 return;
olivierrobin013ba672017-03-01 21:16:243455 }
sdefresnee65fd872016-12-19 13:38:133456
justincohenb1a73cf2017-02-06 20:25:433457 // Don't show the bubble twice (this can happen when tapping very quickly in
3458 // accessibility mode).
3459 if (_pageInfoController)
3460 return;
3461
sdefresnee65fd872016-12-19 13:38:133462 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3463
3464 // Dismiss the omnibox (if open).
3465 [_toolbarController cancelOmniboxEdit];
3466
3467 [[NSNotificationCenter defaultCenter]
3468 postNotificationName:ios_internal::kPageInfoWillShowNotification
3469 object:nil];
3470
3471 // TODO(rohitrao): Get rid of PageInfoModel completely.
3472 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3473 PageInfoModel* pageInfoModel = new PageInfoModel(
3474 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3475
3476 UIView* view = [self view];
stkhapuginc9eee7b2017-04-10 15:49:273477 _pageInfoController = [[PageInfoViewController alloc]
sdefresnee65fd872016-12-19 13:38:133478 initWithModel:pageInfoModel
3479 bridge:bridge
3480 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
stkhapuginc9eee7b2017-04-10 15:49:273481 parentView:view];
3482 bridge->set_controller(_pageInfoController);
sdefresnee65fd872016-12-19 13:38:133483}
3484
3485- (void)hidePageInfoPopupForView:(UIView*)sourceView {
3486 [_pageInfoController dismiss];
stkhapuginc9eee7b2017-04-10 15:49:273487 _pageInfoController = nil;
sdefresnee65fd872016-12-19 13:38:133488}
3489
3490- (void)showSecurityHelpPage {
3491 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3492 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133493 inBackground:NO
3494 appendTo:kCurrentTab];
3495 [self hidePageInfoPopupForView:nil];
3496}
3497
3498- (void)showTabHistoryPopupForBackwardHistory {
3499 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133500
3501 // Dismiss the omnibox (if open).
3502 [_toolbarController cancelOmniboxEdit];
3503 // Dismiss the soft keyboard (if open).
3504 Tab* tab = [_model currentTab];
3505 [tab.webController dismissKeyboard];
3506
kkhorimoto1c12cbf2017-03-14 02:57:133507 web::NavigationItemList backwardItems =
3508 [tab navigationManager]->GetBackwardItems();
sdefresnee65fd872016-12-19 13:38:133509 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133510 withItems:backwardItems
sdefresnee65fd872016-12-19 13:38:133511 forBackHistory:YES];
3512}
3513
3514- (void)showTabHistoryPopupForForwardHistory {
3515 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133516
3517 // Dismiss the omnibox (if open).
3518 [_toolbarController cancelOmniboxEdit];
3519 // Dismiss the soft keyboard (if open).
3520 Tab* tab = [_model currentTab];
3521 [tab.webController dismissKeyboard];
3522
kkhorimoto1c12cbf2017-03-14 02:57:133523 web::NavigationItemList forwardItems =
3524 [tab navigationManager]->GetForwardItems();
sdefresnee65fd872016-12-19 13:38:133525 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133526 withItems:forwardItems
sdefresnee65fd872016-12-19 13:38:133527 forBackHistory:NO];
3528}
3529
3530- (void)navigateToSelectedEntry:(id)sender {
3531 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
3532 TabHistoryCell* selectedCell = (TabHistoryCell*)sender;
kkhorimoto7aed9e262017-03-04 02:28:553533 [[_model currentTab] goToItem:selectedCell.item];
sdefresnee65fd872016-12-19 13:38:133534 [_toolbarController dismissTabHistoryPopup];
3535}
3536
3537- (void)print {
3538 Tab* currentTab = [_model currentTab];
3539 // The UI should prevent users from printing non-printable pages. However, a
3540 // redirection to an un-printable page can happen before it is reflected in
3541 // the UI.
3542 if (![currentTab viewForPrinting]) {
pinkerton07e27842017-03-02 15:29:023543 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
sdefresnee65fd872016-12-19 13:38:133544 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
3545 return;
3546 }
3547 DCHECK(_browserState);
stkhapuginc9eee7b2017-04-10 15:49:273548 if (!_printController) {
3549 _printController = [[PrintController alloc]
3550 initWithContextGetter:_browserState->GetRequestContext()];
sdefresnee65fd872016-12-19 13:38:133551 }
3552 [_printController printView:[currentTab viewForPrinting]
3553 withTitle:[currentTab title]
3554 viewController:self];
3555}
3556
3557- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133558 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3559
3560 ReadingListModel* readingModel =
3561 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013562 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3563 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133564
pinkerton07e27842017-03-02 15:29:023565 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033566 [self showSnackbar:l10n_util::GetNSString(
3567 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133568}
3569
3570#pragma mark - Keyboard commands management
3571
3572- (BOOL)shouldRegisterKeyboardCommands {
3573 if ([self presentedViewController])
3574 return NO;
3575
3576 if (_voiceSearchController && _voiceSearchController->IsVisible())
3577 return NO;
3578
3579 // If there is no first responder, try to make the webview the first
3580 // responder.
3581 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273582 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133583 }
3584
3585 return YES;
3586}
3587
3588- (KeyCommandsProvider*)keyCommandsProvider {
3589 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273590 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133591 }
stkhapuginc9eee7b2017-04-10 15:49:273592 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133593}
3594
3595#pragma mark - KeyCommandsPlumbing
3596
3597- (BOOL)isOffTheRecord {
3598 return _isOffTheRecord;
3599}
3600
3601- (NSUInteger)tabsCount {
3602 return [_model count];
3603}
3604
lpromero47ea8862017-01-13 17:51:063605- (BOOL)canGoBack {
3606 return [_model currentTab].canGoBack;
3607}
3608
3609- (BOOL)canGoForward {
3610 return [_model currentTab].canGoForward;
3611}
3612
sdefresnee65fd872016-12-19 13:38:133613- (void)focusTabAtIndex:(NSUInteger)index {
3614 if ([_model count] > index) {
3615 [_model setCurrentTab:[_model tabAtIndex:index]];
3616 }
3617}
3618
3619- (void)focusNextTab {
3620 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3621 NSInteger modelCount = [_model count];
3622 if (currentTabIndex < modelCount - 1) {
3623 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3624 [_model setCurrentTab:nextTab];
3625 } else {
3626 [_model setCurrentTab:[_model tabAtIndex:0]];
3627 }
3628}
3629
3630- (void)focusPreviousTab {
3631 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3632 if (currentTabIndex > 0) {
3633 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3634 [_model setCurrentTab:previousTab];
3635 } else {
3636 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3637 [_model setCurrentTab:lastTab];
3638 }
3639}
3640
3641- (void)reopenClosedTab {
3642 sessions::TabRestoreService* const tabRestoreService =
3643 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3644 if (!tabRestoreService || tabRestoreService->entries().empty())
3645 return;
3646
3647 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3648 tabRestoreService->entries().front();
3649 // Only handle the TAB type.
3650 if (entry->type != sessions::TabRestoreService::TAB)
3651 return;
3652
3653 [self chromeExecuteCommand:[GenericChromeCommand commandWithTag:IDC_NEW_TAB]];
3654 TabRestoreServiceDelegateImplIOS* const delegate =
3655 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3656 _browserState);
3657 tabRestoreService->RestoreEntryById(delegate, entry->id,
3658 WindowOpenDisposition::CURRENT_TAB);
3659}
3660
3661- (void)focusOmnibox {
3662 [_toolbarController focusOmnibox];
3663}
3664
3665#pragma mark - UIResponder
3666
3667- (NSArray*)keyCommands {
3668 if (![self shouldRegisterKeyboardCommands]) {
3669 return nil;
3670 }
3671 return [self.keyCommandsProvider
3672 keyCommandsForConsumer:self
3673 editingText:![self isFirstResponder]];
3674}
3675
3676#pragma mark -
3677
3678// Induce an intentional crash in the browser process.
3679- (void)induceBrowserCrash {
3680 CHECK(false);
3681 // Call another function, so that the above CHECK can't be tail-call
3682 // optimized. This ensures that this method's name will show up in the stack
3683 // for easier identification.
3684 CHECK(true);
3685}
3686
3687- (void)loadURL:(const GURL&)url
3688 referrer:(const web::Referrer&)referrer
3689 transition:(ui::PageTransition)transition
3690 rendererInitiated:(BOOL)rendererInitiated {
3691 [[OmniboxGeolocationController sharedInstance]
3692 locationBarDidSubmitURL:url
3693 transition:transition
3694 browserState:_browserState];
3695
3696 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3697 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3698 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3699 }
3700
3701 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243702 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133703 // restart.
3704 if (url.host() == kChromeUIBrowserCrashHost) {
3705 [self induceBrowserCrash];
3706 // In debug the app can continue working even after the CHECK. Adding a
3707 // return avoids the crash url to be added to the history.
3708 return;
3709 }
3710
3711 if (url == [_preloadController prerenderedURL]) {
sdefresne2c600c52017-04-04 16:49:593712 std::unique_ptr<web::WebState> newWebState =
3713 [_preloadController releasePrerenderContents];
3714 DCHECK(newWebState);
3715
sdefresnee65fd872016-12-19 13:38:133716 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593717 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133718 DCHECK(oldTab);
3719 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593720
kkhorimotod804c5732017-03-15 23:44:523721 bool canPruneItems =
3722 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593723
kkhorimotod804c5732017-03-15 23:44:523724 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523725 [newTab navigationManager]->CopyStateFromAndPrune(
3726 [oldTab navigationManager]);
sdefresnee65fd872016-12-19 13:38:133727 [[newTab nativeAppNavigationController]
3728 copyStateFrom:[oldTab nativeAppNavigationController]];
sdefresne2c600c52017-04-04 16:49:593729
3730 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3731 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133732
3733 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3734 // BrowserViewController to detect that a pre-rendered tab is switched in,
3735 // and show the prerendering animation.
3736 newTab.isPrerenderTab = NO;
3737
sdefresne2f7781c2017-03-02 19:12:463738 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133739 return;
3740 }
3741 }
3742
3743 GURL urlToLoad = url;
3744 if ([_preloadController hasPrefetchedURL:url]) {
3745 // Prefetched URLs have modified URLs, so load the prefetched version of
3746 // |url| instead of the original |url|.
3747 urlToLoad = [_preloadController prefetchedURL];
3748 }
3749
3750 [_preloadController cancelPrerender];
3751
3752 // Some URLs are not allowed while in incognito. If we are in incognito and
3753 // load a disallowed URL, instead create a new tab not in the incognito state.
3754 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3755 [self webPageOrderedOpen:url
3756 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133757 inIncognito:NO
3758 inBackground:NO
3759 appendTo:kCurrentTab];
3760 return;
3761 }
3762
3763 web::NavigationManager::WebLoadParams params(urlToLoad);
3764 params.referrer = referrer;
3765 params.transition_type = transition;
3766 params.is_renderer_initiated = rendererInitiated;
3767 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233768 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133769}
3770
3771- (void)loadJavaScriptFromLocationBar:(NSString*)script {
3772 [_preloadController cancelPrerender];
3773 DCHECK([_model currentTab]);
3774 [[_model currentTab].webController executeUserJavaScript:script
3775 completionHandler:nil];
3776}
3777
3778- (web::WebState*)currentWebState {
3779 return [[_model currentTab] webState];
3780}
3781
3782// This is called from within an animation block.
3783- (void)toolbarHeightChanged {
3784 if ([self headerHeight] != 0) {
3785 // Ensure full screen height is updated.
3786 Tab* currentTab = [_model currentTab];
3787 BOOL visible = self.isToolbarOnScreen;
3788 [currentTab updateFullscreenWithToolbarVisible:visible];
3789 }
3790}
3791
3792// Load a new URL on a new page/tab.
3793- (void)webPageOrderedOpen:(const GURL&)URL
3794 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133795 inBackground:(BOOL)inBackground
3796 appendTo:(OpenPosition)appendTo {
3797 Tab* adjacentTab = nil;
3798 if (appendTo == kCurrentTab)
3799 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353800 [_model insertTabWithURL:URL
3801 referrer:referrer
3802 transition:ui::PAGE_TRANSITION_LINK
3803 opener:adjacentTab
3804 openedByDOM:NO
3805 atIndex:TabModelConstants::kTabPositionAutomatically
3806 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133807}
3808
3809- (void)webPageOrderedOpen:(const GURL&)url
3810 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133811 inIncognito:(BOOL)inIncognito
3812 inBackground:(BOOL)inBackground
3813 appendTo:(OpenPosition)appendTo {
3814 if (inIncognito == _isOffTheRecord) {
3815 [self webPageOrderedOpen:url
3816 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133817 inBackground:inBackground
3818 appendTo:appendTo];
3819 return;
3820 }
3821 // When sending an open command that switches modes, ensure the tab
3822 // ends up appended to the end of the model, not just next to what is
3823 // currently selected in the other mode. This is done with the |append|
3824 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273825 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133826 initWithURL:url
3827 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133828 inIncognito:inIncognito
3829 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273830 appendTo:kLastTab];
sdefresnee65fd872016-12-19 13:38:133831 [self chromeExecuteCommand:command];
3832}
3833
3834- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
3835 [[_model currentTab] loadSessionTab:sessionTab];
3836}
3837
3838- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433839 DCHECK(javascript);
3840 javascript = [javascript stringByRemovingPercentEncoding];
3841 web::WebState* webState = [[_model currentTab] webState];
3842 if (webState) {
3843 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3844 }
sdefresnee65fd872016-12-19 13:38:133845}
3846
3847#pragma mark - WebToolbarDelegate methods
3848
3849- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3850 if (_locationBarHasFocus)
3851 return; // TODO(crbug.com/244366): This should not be necessary.
3852 _locationBarHasFocus = YES;
3853 [[NSNotificationCenter defaultCenter]
3854 postNotificationName:ios_internal::
3855 kLocationBarBecomesFirstResponderNotification
3856 object:nil];
3857 [_sideSwipeController setEnabled:NO];
3858 if ([[_model currentTab].webController wantsKeyboardShield]) {
3859 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3860 [_typingShield setAlpha:0.0];
3861 [_typingShield setHidden:NO];
3862 [UIView animateWithDuration:0.3
3863 animations:^{
3864 [_typingShield setAlpha:1.0];
3865 }];
3866 }
3867 [[OmniboxGeolocationController sharedInstance]
3868 locationBarDidBecomeFirstResponder:_browserState];
3869}
3870
3871- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3872 if (!_locationBarHasFocus)
3873 return; // TODO(crbug.com/244366): This should not be necessary.
3874 _locationBarHasFocus = NO;
3875 [_sideSwipeController setEnabled:YES];
3876 [[NSNotificationCenter defaultCenter]
3877 postNotificationName:ios_internal::
3878 kLocationBarResignsFirstResponderNotification
3879 object:nil];
3880 [UIView animateWithDuration:0.3
3881 animations:^{
3882 [_typingShield setAlpha:0.0];
3883 }
3884 completion:^(BOOL finished) {
3885 // This can happen if one quickly resigns the omnibox and then taps
3886 // on the omnibox again during this animation. If the animation is
3887 // interrupted and the toolbar controller is first responder, it's safe
3888 // to assume the |_typingShield| shouldn't be hidden here.
3889 if (!finished && [_toolbarController isOmniboxFirstResponder])
3890 return;
3891 [_typingShield setHidden:YES];
3892 }];
3893 [[OmniboxGeolocationController sharedInstance]
3894 locationBarDidResignFirstResponder:_browserState];
3895
3896 // If a load was cancelled by an omnibox edit, but nothing is loading when
3897 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3898 if (_locationBarEditCancelledLoad) {
3899 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:293900
3901 web::WebState* webState = [_model currentTab].webState;
3902 if (!_toolbarModelIOS->IsLoading() && webState)
3903 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3904 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133905 }
3906}
3907
3908- (IBAction)locationBarBeganEdit:(id)sender {
3909 // On handsets, if a page is currently loading it should be stopped.
3910 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
stkhapuginc9eee7b2017-04-10 15:49:273911 GenericChromeCommand* command =
3912 [[GenericChromeCommand alloc] initWithTag:IDC_STOP];
sdefresnee65fd872016-12-19 13:38:133913 [self chromeExecuteCommand:command];
3914 _locationBarEditCancelledLoad = YES;
3915 }
3916}
3917
3918- (IBAction)prepareToEnterTabSwitcher:(id)sender {
3919 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
3920}
3921
3922- (ToolbarModelIOS*)toolbarModelIOS {
3923 return _toolbarModelIOS.get();
3924}
3925
3926- (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
3927 [_toolbarController setBackgroundAlpha:alpha];
3928}
3929
3930- (void)updateToolbarControlsAlpha:(CGFloat)alpha {
3931 [_toolbarController setControlsAlpha:alpha];
3932}
3933
3934- (void)willUpdateToolbarSnapshot {
3935 [[_model currentTab].overscrollActionsController clear];
3936}
3937
3938- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
3939 CGRect frame = [_contentArea frame];
3940 if (!fullScreen) {
3941 // Changing the origin here is unnecessary, it's set in page_animation_util.
3942 frame.size.height -= [self headerHeight];
3943 }
3944
3945 CGFloat shortAxis = frame.size.width;
3946 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
3947 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
3948 CGFloat aspectRatio = frame.size.height / frame.size.width;
3949 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
3950 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
3951 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
3952 CGRect cardFrame = {frame.origin, cardSize};
3953
3954 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:173955 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133956 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
3957 : CardCloseButtonSide::LEADING;
3958 [_contentArea addSubview:card];
3959 return card;
3960}
3961
3962#pragma mark - Command Handling
3963
3964- (IBAction)chromeExecuteCommand:(id)sender {
3965 NSInteger command = [sender tag];
3966
3967 if (!_model || !_browserState)
3968 return;
rohitrao005a6432017-03-16 20:52:423969 Tab* currentTab = [_model currentTab];
sdefresnee65fd872016-12-19 13:38:133970
3971 switch (command) {
3972 case IDC_BACK:
lpromero47ea8862017-01-13 17:51:063973 [[_model currentTab] goBack];
sdefresnee65fd872016-12-19 13:38:133974 break;
3975 case IDC_BOOKMARK_PAGE:
3976 [self initializeBookmarkInteractionController];
3977 [_bookmarkInteractionController
3978 presentBookmarkForTab:[_model currentTab]
3979 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
3980 inView:[_toolbarController bookmarkButtonView]
3981 originRect:[_toolbarController bookmarkButtonAnchorRect]];
3982 break;
3983 case IDC_CLOSE_TAB:
3984 [self closeCurrentTab];
3985 break;
3986 case IDC_FIND:
3987 [self initFindBarForTab];
3988 break;
rohitraob2bf3cb2017-02-10 14:10:363989 case IDC_FIND_NEXT: {
rohitrao005a6432017-03-16 20:52:423990 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:133991 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:423992 FindTabHelper::FromWebState(currentTab.webState)
3993 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
3994 [_findBarController updateResultsCount:model];
3995 });
sdefresnee65fd872016-12-19 13:38:133996 break;
rohitraob2bf3cb2017-02-10 14:10:363997 }
3998 case IDC_FIND_PREVIOUS: {
rohitrao005a6432017-03-16 20:52:423999 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134000 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424001 FindTabHelper::FromWebState(currentTab.webState)
4002 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4003 [_findBarController updateResultsCount:model];
4004 });
sdefresnee65fd872016-12-19 13:38:134005 break;
rohitraob2bf3cb2017-02-10 14:10:364006 }
sdefresnee65fd872016-12-19 13:38:134007 case IDC_FIND_CLOSE:
4008 [self closeFindInPage];
4009 break;
4010 case IDC_FIND_UPDATE:
4011 [self searchFindInPage];
4012 break;
4013 case IDC_FORWARD:
lpromero47ea8862017-01-13 17:51:064014 [[_model currentTab] goForward];
sdefresnee65fd872016-12-19 13:38:134015 break;
4016 case IDC_FULLSCREEN:
4017 NOTIMPLEMENTED();
4018 break;
4019 case IDC_HELP_PAGE_VIA_MENU:
4020 [self showHelpPage];
4021 break;
4022 case IDC_NEW_TAB:
4023 if (_isOffTheRecord) {
4024 // Not for this browser state, send it on its way.
4025 [super chromeExecuteCommand:sender];
4026 } else {
4027 [self newTab:sender];
4028 }
4029 break;
4030 case IDC_PRELOAD_VOICE_SEARCH:
4031 // Preload VoiceSearchController and views and view controllers needed
4032 // for voice search.
4033 [self ensureVoiceSearchControllerCreated];
4034 _voiceSearchController->PrepareToAppear();
4035 break;
4036 case IDC_NEW_INCOGNITO_TAB:
4037 if (_isOffTheRecord) {
4038 [self newTab:sender];
4039 } else {
4040 // Not for this browser state, send it on its way.
4041 [super chromeExecuteCommand:sender];
4042 }
4043 break;
liaoyuke563dc4a2017-03-17 18:36:294044 case IDC_RELOAD: {
4045 web::WebState* webState = [_model currentTab].webState;
4046 if (webState)
4047 // |check_for_repost| is true because the reload is explicitly initiated
4048 // by the user.
4049 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4050 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134051 break;
liaoyuke563dc4a2017-03-17 18:36:294052 }
sdefresnee65fd872016-12-19 13:38:134053 case IDC_SHARE_PAGE:
4054 [self sharePage];
4055 break;
4056 case IDC_SHOW_MAIL_COMPOSER:
4057 [self showMailComposer:sender];
4058 break;
4059 case IDC_READER_MODE:
4060 [[_model currentTab] switchToReaderMode];
4061 break;
4062 case IDC_REQUEST_DESKTOP_SITE:
liaoyuke6ab362012017-04-12 16:10:074063 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
sdefresnee65fd872016-12-19 13:38:134064 break;
liaoyukeea9f3ee62017-03-07 22:05:394065 case IDC_REQUEST_MOBILE_SITE:
Yuke Liao705611d2017-06-01 18:03:064066 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
liaoyukeea9f3ee62017-03-07 22:05:394067 break;
sdefresnee65fd872016-12-19 13:38:134068 case IDC_SHOW_TOOLS_MENU: {
jif7fed8122017-02-08 13:15:254069 [self showToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134070 break;
4071 }
4072 case IDC_SHOW_BOOKMARK_MANAGER: {
4073 if (IsIPadIdiom()) {
4074 [self showAllBookmarks];
4075 } else {
4076 [self initializeBookmarkInteractionController];
4077 [_bookmarkInteractionController presentBookmarks];
4078 }
4079 break;
4080 }
4081 case IDC_SHOW_OTHER_DEVICES: {
4082 if (IsIPadIdiom()) {
4083 [self showNTPPanel:NewTabPage::kOpenTabsPanel];
4084 } else {
4085 UIViewController* controller = [RecentTabsPanelViewController
4086 controllerToPresentForBrowserState:_browserState
4087 loader:self];
4088 controller.modalPresentationStyle = UIModalPresentationFormSheet;
4089 controller.modalPresentationCapturesStatusBarAppearance = YES;
4090 [self presentViewController:controller animated:YES completion:nil];
4091 }
4092 break;
4093 }
4094 case IDC_STOP:
sdefresne7d699dd2017-04-05 13:05:234095 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:134096 break;
4097#if !defined(NDEBUG)
4098 case IDC_VIEW_SOURCE:
4099 [self viewSource];
4100 break;
4101#endif
4102 case IDC_SHOW_PAGE_INFO:
4103 DCHECK([sender isKindOfClass:[UIButton class]]);
4104 [self showPageInfoPopupForView:sender];
4105 break;
4106 case IDC_HIDE_PAGE_INFO:
4107 [[NSNotificationCenter defaultCenter]
4108 postNotificationName:ios_internal::kPageInfoWillHideNotification
4109 object:nil];
4110 [self hidePageInfoPopupForView:sender];
4111 break;
4112 case IDC_SHOW_SECURITY_HELP:
4113 [self showSecurityHelpPage];
4114 break;
4115 case IDC_SHOW_BACK_HISTORY:
4116 [self showTabHistoryPopupForBackwardHistory];
4117 break;
4118 case IDC_SHOW_FORWARD_HISTORY:
4119 [self showTabHistoryPopupForForwardHistory];
4120 break;
4121 case IDC_BACK_FORWARD_IN_TAB_HISTORY:
4122 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
4123 [self navigateToSelectedEntry:sender];
4124 break;
4125 case IDC_PRINT:
4126 [self print];
4127 break;
4128 case IDC_ADD_READING_LIST: {
sdefresnee65fd872016-12-19 13:38:134129 ReadingListAddCommand* command =
4130 base::mac::ObjCCastStrict<ReadingListAddCommand>(sender);
4131 [self addToReadingListURL:[command URL] title:[command title]];
4132 break;
4133 }
4134 case IDC_RATE_THIS_APP:
4135 [self showRateThisAppDialog];
4136 break;
4137 case IDC_SHOW_READING_LIST:
sdefresnee65fd872016-12-19 13:38:134138 [self showReadingList];
4139 break;
4140 case IDC_VOICE_SEARCH:
4141 // If the voice search command is coming from a UIView sender, store it
4142 // before sending the command up the responder chain.
4143 if ([sender isKindOfClass:[UIView class]])
stkhapuginc9eee7b2017-04-10 15:49:274144 _voiceSearchButton = sender;
sdefresnee65fd872016-12-19 13:38:134145 [super chromeExecuteCommand:sender];
4146 break;
4147 case IDC_SHOW_QR_SCANNER:
jif5f067c62017-02-03 17:36:434148 [self showQRScanner];
sdefresnee65fd872016-12-19 13:38:134149 break;
gambardd2e44fb2017-01-25 09:14:214150 case IDC_SHOW_SUGGESTIONS:
4151 if (experimental_flags::IsSuggestionsUIEnabled()) {
4152 [self showSuggestionsUI];
4153 }
4154 break;
sdefresnee65fd872016-12-19 13:38:134155 default:
4156 // Unknown commands get sent up the responder chain.
4157 [super chromeExecuteCommand:sender];
4158 break;
4159 }
4160}
4161
4162- (void)closeCurrentTab {
4163 Tab* currentTab = [_model currentTab];
4164 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4165 if (tabIndex == NSNotFound)
4166 return;
4167
jif7fed8122017-02-08 13:15:254168 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4169 // iPad.
sdefresnee65fd872016-12-19 13:38:134170 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4171 exitingPage.image =
4172 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4173
4174 // Close the actual tab, and add its image as a subview.
4175 [_model closeTabAtIndex:tabIndex];
4176
4177 // Do not animate close in iPad.
4178 if (!IsIPadIdiom()) {
4179 [_contentArea addSubview:exitingPage];
4180 ios_internal::page_animation_util::AnimateOutWithCompletion(
4181 exitingPage, 0, YES, IsPortrait(), ^{
4182 [exitingPage removeFromSuperview];
4183 });
4184 }
4185}
4186
sdefresnee65fd872016-12-19 13:38:134187- (void)sharePage {
jif4a8cf942017-02-03 12:05:244188 ShareToData* data = activity_services::ShareToDataForTab([_model currentTab]);
sdefresnee65fd872016-12-19 13:38:134189 if (data)
4190 [self sharePageWithData:data];
4191}
4192
4193- (void)sharePageWithData:(ShareToData*)data {
4194 id<ShareProtocol> controller = [_dependencyFactory shareControllerInstance];
4195 if ([controller isActive])
4196 return;
4197 CGRect fromRect = [_toolbarController shareButtonAnchorRect];
4198 UIView* inView = [_toolbarController shareButtonView];
4199 [controller shareWithData:data
4200 controller:self
4201 browserState:_browserState
4202 shareToDelegate:self
4203 fromRect:fromRect
4204 inView:inView];
4205}
4206
4207- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
4208 [[_dependencyFactory shareControllerInstance] cancelShareAnimated:NO];
4209 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4210 [_bookmarkInteractionController dismissSnackbar];
4211 [_toolbarController cancelOmniboxEdit];
4212 [_dialogPresenter cancelAllDialogs];
4213 [self hidePageInfoPopupForView:nil];
4214 if (_voiceSearchController)
4215 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364216
4217 Tab* currentTab = [_model currentTab];
4218 [currentTab dismissModals];
4219
rohitrao005a6432017-03-16 20:52:424220 if (currentTab) {
4221 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4222 if (findHelper) {
4223 findHelper->StopFinding(^{
4224 [self updateFindBar:NO shouldFocus:NO];
4225 });
4226 }
4227 }
rohitraob2bf3cb2017-02-10 14:10:364228
sdefresnee65fd872016-12-19 13:38:134229 [_contextualSearchController movePanelOffscreen];
sdefresnee65fd872016-12-19 13:38:134230 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134231 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274232 _printController = nil;
jif7fed8122017-02-08 13:15:254233 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134234 [_contextMenuCoordinator stop];
4235 [self dismissRateThisAppDialog];
4236
gambardd2e44fb2017-01-25 09:14:214237 [_contentSuggestionsCoordinator stop];
4238
sdefresnee65fd872016-12-19 13:38:134239 if (self.presentedViewController) {
4240 // Dismisses any other modal controllers that may be present, e.g. Recent
4241 // Tabs.
4242 // Note that currently, some controllers like the bookmark ones were already
4243 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4244 // but are still reported as the presentedViewController. The result is that
4245 // this will call -dismissViewControllerAnimated:completion: a second time
4246 // on it. It is not per se an issue, as it is a no-op. The problem is that
4247 // in such a case, the completion block is not called.
4248 // To ensure the completion is called, nil is passed here, and the
4249 // completion is called below.
4250 [self dismissViewControllerAnimated:NO completion:nil];
4251 // Dismissed controllers will be so after a delay. Queue the completion
4252 // callback after that.
4253 if (completion) {
4254 dispatch_after(
4255 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4256 dispatch_get_main_queue(), ^{
4257 completion();
4258 });
4259 }
4260 } else if (completion) {
4261 // If no view controllers are presented, we should be ok with dispatching
4262 // the completion block directly.
4263 dispatch_async(dispatch_get_main_queue(), completion);
4264 }
4265}
4266
4267- (void)showHelpPage {
4268 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4269 [self webPageOrderedOpen:helpUrl
4270 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134271 inBackground:NO
4272 appendTo:kCurrentTab];
4273}
4274
sdefresnee65fd872016-12-19 13:38:134275- (void)resetAllWebViews {
eugenebutf8a138e62017-01-24 22:41:344276 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:134277 [_model resetAllWebViews];
4278}
4279
4280#pragma mark - Find Bar
4281
4282- (void)hideFindBarWithAnimation:(BOOL)animate {
4283 [_findBarController hideFindBarView:animate];
4284}
4285
4286- (void)showFindBarWithAnimation:(BOOL)animate
4287 selectText:(BOOL)selectText
4288 shouldFocus:(BOOL)shouldFocus {
4289 DCHECK(_findBarController);
4290 Tab* tab = [_model currentTab];
4291 DCHECK(tab);
4292 CRWWebController* webController = tab.webController;
4293
4294 CGRect referenceFrame = CGRectZero;
4295 if (IsIPadIdiom()) {
4296 referenceFrame = webController.visibleFrame;
4297 referenceFrame.origin.y -= kIPadFindBarOverlap;
4298 } else {
4299 referenceFrame = _contentArea.frame;
4300 }
4301
4302 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4303 [_findBarController addFindBarView:animate
4304 intoView:self.view
4305 withFrame:referenceFrame
4306 alignWithFrame:omniboxFrame
4307 selectText:selectText];
4308 [self updateFindBar:YES shouldFocus:shouldFocus];
4309}
4310
4311// Create find bar controller and pass it to the web controller.
4312- (void)initFindBarForTab {
4313 if (!self.canShowFindBar)
4314 return;
4315
4316 if (!_findBarController)
stkhapuginc9eee7b2017-04-10 15:49:274317 _findBarController =
4318 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134319
4320 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:424321 DCHECK(tab);
4322 auto* helper = FindTabHelper::FromWebState(tab.webState);
4323 DCHECK(!helper->IsFindUIActive());
4324 helper->SetFindUIActive(true);
sdefresnee65fd872016-12-19 13:38:134325 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4326}
4327
4328- (void)searchFindInPage {
rohitrao005a6432017-03-16 20:52:424329 DCHECK([_model currentTab]);
4330 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
stkhapuginc9eee7b2017-04-10 15:49:274331 __weak BrowserViewController* weakSelf = self;
4332 helper->StartFinding(
4333 [_findBarController searchTerm], ^(FindInPageModel* model) {
4334 BrowserViewController* strongSelf = weakSelf;
4335 if (!strongSelf) {
4336 return;
4337 }
4338 [strongSelf->_findBarController updateResultsCount:model];
4339 });
rohitrao005a6432017-03-16 20:52:424340
sdefresnee65fd872016-12-19 13:38:134341 if (!_isOffTheRecord)
rohitrao005a6432017-03-16 20:52:424342 helper->PersistSearchTerm();
sdefresnee65fd872016-12-19 13:38:134343}
4344
4345- (void)closeFindInPage {
stkhapuginc9eee7b2017-04-10 15:49:274346 __weak BrowserViewController* weakSelf = self;
rohitrao005a6432017-03-16 20:52:424347 Tab* currentTab = [_model currentTab];
4348 if (currentTab) {
4349 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4350 [weakSelf updateFindBar:NO shouldFocus:NO];
4351 });
4352 }
sdefresnee65fd872016-12-19 13:38:134353}
4354
4355- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
rohitrao005a6432017-03-16 20:52:424356 DCHECK([_model currentTab]);
4357 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4358 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134359 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424360 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134361 }
4362
4363 [self setFramesForHeaders:[self headerViews]
4364 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424365 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134366 initialUpdate:initialUpdate
4367 focusTextfield:shouldFocus];
4368 } else {
4369 [self hideFindBarWithAnimation:YES];
4370 }
4371}
4372
4373- (void)showAllBookmarks {
4374 DCHECK(self.visible || self.dismissingModal);
4375 GURL URL(kChromeUIBookmarksURL);
4376 Tab* tab = [_model currentTab];
4377 web::NavigationManager::WebLoadParams params(URL);
4378 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234379 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134380}
4381
4382- (void)showReadingList {
stkhapuginc9eee7b2017-04-10 15:49:274383 _readingListCoordinator = [[ReadingListCoordinator alloc]
gambard6299cc1d2017-02-21 13:06:034384 initWithBaseViewController:self
4385 browserState:self.browserState
stkhapuginc9eee7b2017-04-10 15:49:274386 loader:self];
gambard6299cc1d2017-02-21 13:06:034387
4388 [_readingListCoordinator start];
sdefresnee65fd872016-12-19 13:38:134389}
4390
4391- (void)showQRScanner {
stkhapuginc9eee7b2017-04-10 15:49:274392 _qrScannerViewController =
4393 [[QRScannerViewController alloc] initWithDelegate:_toolbarController];
sdefresnee65fd872016-12-19 13:38:134394 [self presentViewController:[_qrScannerViewController
4395 getViewControllerToPresent]
4396 animated:YES
4397 completion:nil];
4398}
4399
gambardd2e44fb2017-01-25 09:14:214400- (void)showSuggestionsUI {
4401 if (!_contentSuggestionsCoordinator) {
stkhapuginc9eee7b2017-04-10 15:49:274402 _contentSuggestionsCoordinator =
4403 [[ContentSuggestionsCoordinator alloc] initWithBaseViewController:self];
gambard9b6abde2017-02-20 12:13:554404 [_contentSuggestionsCoordinator setURLLoader:self];
gambardd2e44fb2017-01-25 09:14:214405 }
gambard0ac7f3e2017-02-01 14:53:144406 [_contentSuggestionsCoordinator setBrowserState:_browserState];
gambardd2e44fb2017-01-25 09:14:214407 [_contentSuggestionsCoordinator start];
4408}
4409
sdefresnee65fd872016-12-19 13:38:134410- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel {
4411 DCHECK(self.visible || self.dismissingModal);
4412 GURL url(kChromeUINewTabURL);
4413 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4414 if (fragment != "") {
4415 GURL::Replacements replacement;
4416 replacement.SetRefStr(fragment);
4417 url = url.ReplaceComponents(replacement);
4418 }
4419 Tab* tab = [_model currentTab];
4420 web::NavigationManager::WebLoadParams params(url);
4421 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234422 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134423}
4424
4425- (void)showRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274426 DCHECK(!_rateThisAppDialog);
sdefresnee65fd872016-12-19 13:38:134427
4428 // Store the current timestamp whenever this dialog is shown.
4429 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4430 base::Time::Now().ToInternalValue());
4431
4432 // Some versions of iOS7 do not support linking directly to the "Ratings and
4433 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4434 // links to the main appstore page for the Chrome app.
4435 NSURL* storeURL =
4436 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4437 @"MZStore.woa/wa/"
4438 @"viewContentsUserReviews?type=Purple+Software&id="
4439 @"535886823&pt=9008&ct=rating")];
4440
4441 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4442 [self clearPresentedStateWithCompletion:nil];
4443
stkhapuginc9eee7b2017-04-10 15:49:274444 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
sdefresnee65fd872016-12-19 13:38:134445 [_rateThisAppDialog setAppStoreURL:storeURL];
4446 [_rateThisAppDialog setDelegate:self];
4447 [_rateThisAppDialog show];
4448}
4449
4450- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274451 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134452 base::RecordAction(base::UserMetricsAction(
4453 "IOSRateThisAppDialogDismissedProgramatically"));
4454 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274455 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134456 }
4457}
4458
4459#if !defined(NDEBUG)
4460- (void)viewSource {
4461 Tab* tab = [_model currentTab];
4462 DCHECK(tab);
4463 CRWWebController* webController = tab.webController;
4464 NSString* script = @"document.documentElement.outerHTML;";
stkhapuginc9eee7b2017-04-10 15:49:274465 __weak Tab* weakTab = tab;
4466 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:134467 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
stkhapuginc9eee7b2017-04-10 15:49:274468 Tab* strongTab = weakTab;
sdefresnee65fd872016-12-19 13:38:134469 if (!strongTab)
4470 return;
4471 if (![result isKindOfClass:[NSString class]])
4472 result = @"Not an HTML page";
4473 std::string base64HTML;
4474 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4475 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
kkhorimotob110b262017-06-01 18:38:254476 web::Referrer referrer([strongTab lastCommittedURL],
4477 web::ReferrerPolicyDefault);
eugenebut91cfb3a2017-02-21 16:40:314478
4479 [[weakSelf tabModel]
sdefresnea6395912017-03-01 01:14:354480 insertTabWithURL:URL
4481 referrer:referrer
4482 transition:ui::PAGE_TRANSITION_LINK
4483 opener:strongTab
4484 openedByDOM:YES
4485 atIndex:TabModelConstants::kTabPositionAutomatically
4486 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:134487 };
4488 [webController executeJavaScript:script
4489 completionHandler:completionHandlerBlock];
4490}
4491#endif // !defined(NDEBUG)
4492
4493- (void)startVoiceSearch {
4494 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124495 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134496 _startVoiceSearchAfterNewTabAnimation = YES;
4497 return;
4498 }
4499
4500 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4501 // dismiss the keyboard.
4502 [self closeFindInPage];
4503 [[_model currentTab].webController dismissKeyboard];
4504
4505 // Ensure that voice search objects are created.
4506 [self ensureVoiceSearchControllerCreated];
4507 [self ensureVoiceSearchBarCreated];
4508
4509 // Present voice search.
4510 [_voiceSearchBar prepareToPresentVoiceSearch];
4511 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4512 [_toolbarController cancelOmniboxEdit];
4513}
4514
4515#pragma mark - ToolbarOwner
4516
4517- (ToolbarController*)relinquishedToolbarController {
4518 if (_isToolbarControllerRelinquished)
4519 return nil;
4520
4521 ToolbarController* relinquishedToolbarController = nil;
4522 if ([_toolbarController view].hidden) {
4523 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:254524 if (currentTab && UrlHasChromeScheme(currentTab.lastCommittedURL)) {
sdefresnee65fd872016-12-19 13:38:134525 // Use the native content controller's toolbar when the BVC's is hidden.
4526 id nativeController = [self nativeControllerForTab:currentTab];
4527 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4528 relinquishedToolbarController =
4529 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274530 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134531 }
4532 }
4533 } else {
stkhapuginc9eee7b2017-04-10 15:49:274534 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134535 }
4536 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4537 return relinquishedToolbarController;
4538}
4539
4540- (void)reparentToolbarController {
4541 if (_isToolbarControllerRelinquished) {
4542 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4543 // A native content controller's toolbar has been relinquished.
4544 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274545 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134546 } else if ([_findBarController isFindInPageShown]) {
4547 [self.view insertSubview:[_toolbarController view]
4548 belowSubview:[_findBarController view]];
4549 } else {
4550 [self.view addSubview:[_toolbarController view]];
4551 }
4552 if (_contextualSearchPanel) {
4553 // Move panel back into its correct place.
4554 [self.view insertSubview:_contextualSearchPanel
4555 aboveSubview:[_toolbarController view]];
4556 }
4557 _isToolbarControllerRelinquished = NO;
4558 }
4559}
4560
4561#pragma mark - TabModelObserver methods
4562
4563// Observer method, tab inserted.
4564- (void)tabModel:(TabModel*)model
4565 didInsertTab:(Tab*)tab
4566 atIndex:(NSUInteger)modelIndex
4567 inForeground:(BOOL)fg {
4568 DCHECK(tab);
4569 [self installDelegatesForTab:tab];
4570
4571 if (fg) {
4572 [_contextualSearchController setTab:tab];
4573 [_paymentRequestManager setWebState:tab.webState];
4574 }
4575}
4576
4577// Observer method, active tab changed.
4578- (void)tabModel:(TabModel*)model
4579 didChangeActiveTab:(Tab*)newTab
4580 previousTab:(Tab*)previousTab
4581 atIndex:(NSUInteger)index {
4582 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4583 // Currently this observer method is always called with a non-nil |newTab|,
4584 // but that may change in the future. Remove this DCHECK when it does.
4585 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274586 if (_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:134587 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4588 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4589 }
4590 [self updateVoiceSearchBarVisibilityAnimated:NO];
4591
4592 [_contextualSearchController setTab:newTab];
4593 [_paymentRequestManager setWebState:newTab.webState];
4594
4595 [self tabSelected:newTab];
4596 DCHECK_EQ(newTab, [model currentTab]);
4597 [self installDelegatesForTab:newTab];
4598}
4599
4600// Observer method, tab changed.
4601- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4602 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4603 if (tab == [_model currentTab]) {
4604 [self updateToolbar];
4605 // Disable contextual search when |tab| is a voice search result tab.
4606 BOOL enableContextualSearch = self.active && !tab.isVoiceSearchResultsTab;
4607 [_contextualSearchController enableContextualSearch:enableContextualSearch];
4608 }
4609}
4610
sdefresne49cf2862017-03-15 13:46:144611// Observer method, tab replaced.
4612- (void)tabModel:(TabModel*)model
4613 didReplaceTab:(Tab*)oldTab
4614 withTab:(Tab*)newTab
4615 atIndex:(NSUInteger)index {
4616 [self uninstallDelegatesForTab:oldTab];
4617 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264618
michaeldo79909fb2017-05-09 23:42:504619 if (_infoBarContainer) {
4620 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4621 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4622 }
4623
kkhorimotofa0844cc2017-03-20 17:01:264624 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4625 if (self.active && model.currentTab == newTab)
4626 [self displayTab:newTab isNewSelection:NO];
sdefresne49cf2862017-03-15 13:46:144627}
4628
sdefresnee65fd872016-12-19 13:38:134629// A tab has been removed, remove its views from display if necessary.
4630- (void)tabModel:(TabModel*)model
4631 didRemoveTab:(Tab*)tab
4632 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144633 [self uninstallDelegatesForTab:tab];
4634
sdefresnee65fd872016-12-19 13:38:134635 // Remove stored native controllers for the tab.
4636 [_nativeControllersForTabIDs removeObjectForKey:tab.tabId];
4637
4638 // Ignore changes while the tab stack view is visible (or while suspended).
4639 // The display will be refreshed when this view becomes active again.
4640 if (!self.visible || !model.webUsageEnabled)
4641 return;
4642
4643 // Remove the find bar for now.
4644 [self hideFindBarWithAnimation:NO];
4645}
4646
4647- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4648 if (tab == [model currentTab]) {
4649 [_contentArea displayContentView:nil];
4650 [_toolbarController selectedTabChanged];
4651 }
4652
4653 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4654 if ([model count] == 1) { // About to remove the last tab.
4655 [_contextualSearchController setTab:nil];
4656 [_paymentRequestManager setWebState:nil];
4657 }
4658}
4659
4660// Called when the number of tabs changes. Update the toolbar accordingly.
4661- (void)tabModelDidChangeTabCount:(TabModel*)model {
4662 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134663 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134664}
4665
4666#pragma mark - Upgrade Detection
4667
4668- (void)showUpgrade:(UpgradeCenter*)center {
4669 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274670 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134671 NSString* tabId = tab.tabId;
4672 DCHECK([tab infoBarManager]);
4673 [center addInfoBarToManager:[tab infoBarManager] forTabId:tabId];
4674 }
4675}
4676
4677#pragma mark - ContextualSearchControllerDelegate
4678
4679- (void)createTabFromContextualSearchController:(const GURL&)url {
4680 Tab* currentTab = [_model currentTab];
4681 DCHECK(currentTab);
4682 NSUInteger index = [_model indexOfTab:currentTab];
4683 [self addSelectedTabWithURL:url
4684 atIndex:index + 1
4685 transition:ui::PAGE_TRANSITION_LINK];
4686}
4687
4688- (void)promotePanelToTabProvidedBy:(id<ContextualSearchTabProvider>)tabProvider
4689 focusInput:(BOOL)focusInput {
4690 // Tell the panel it will be promoted.
4691 ContextualSearchPanelView* promotingPanel = _contextualSearchPanel;
4692 [promotingPanel prepareForPromotion];
4693
4694 // Make a new panel and tell the controller about it.
4695 _contextualSearchPanel = [self createPanelView];
4696 [self.view insertSubview:_contextualSearchPanel belowSubview:promotingPanel];
4697 [_contextualSearchController setPanel:_contextualSearchPanel];
4698
4699 // Figure out vertical offset.
4700 CGFloat offset = StatusBarHeight();
4701 if (IsIPadIdiom()) {
4702 offset = MAX(offset, CGRectGetMaxY([_tabStripController view].frame));
4703 }
4704
4705 // Transition steps: Animate the panel position, fade in the toolbar and
4706 // tab strip.
4707 ProceduralBlock transition = ^{
4708 [promotingPanel promoteToMatchSuperviewWithVerticalOffset:offset];
4709 [self updateToolbarControlsAlpha:1.0];
4710 [self updateToolbarBackgroundAlpha:1.0];
4711 [_tabStripController view].alpha = 1.0;
4712 };
4713
4714 // After the transition animation completes, add the tab to the tab model
4715 // (on iPad this triggers the tab strip animation too), then fade out the
4716 // transitioning panel and remove it.
4717 void (^completion)(BOOL) = ^(BOOL finished) {
4718 _contextualSearchMask.alpha = 0;
sdefresne2c600c52017-04-04 16:49:594719 std::unique_ptr<web::WebState> webState = [tabProvider releaseWebState];
4720 DCHECK(webState);
4721 DCHECK(webState->GetNavigationManager());
4722
4723 Tab* newTab = LegacyTabHelper::GetTabForWebState(webState.get());
4724 WebStateList* webStateList = [_model webStateList];
4725
4726 // Insert the new tab after the current tab.
4727 DCHECK_NE(webStateList->active_index(), WebStateList::kInvalidIndex);
4728 DCHECK_NE(webStateList->active_index(), INT_MAX);
4729 int insertion_index = webStateList->active_index() + 1;
4730 webStateList->InsertWebState(insertion_index, std::move(webState));
4731 webStateList->SetOpenerOfWebStateAt(insertion_index,
4732 [tabProvider webStateOpener]);
sdefresnee65fd872016-12-19 13:38:134733
4734 // Set isPrerenderTab to NO after inserting the tab. This will allow the
4735 // BrowserViewController to detect that a pre-rendered tab is switched in,
4736 // and show the prerendering animation. This needs to happen before the
4737 // tab is made the current tab.
4738 // This also enables contextual search (if otherwise applicable) on
4739 // |newTab|.
sdefresne2c600c52017-04-04 16:49:594740
sdefresnee65fd872016-12-19 13:38:134741 newTab.isPrerenderTab = NO;
4742 [_model setCurrentTab:newTab];
4743
sdefresne2f7781c2017-03-02 19:12:464744 if (newTab.loadFinished)
sdefresnee65fd872016-12-19 13:38:134745 [self tabLoadComplete:newTab withSuccess:YES];
4746
4747 if (focusInput) {
4748 [_toolbarController focusOmnibox];
4749 }
4750 _infoBarContainer->RestoreInfobars();
4751
4752 [UIView animateWithDuration:ios::material::kDuration2
4753 animations:^{
4754 promotingPanel.alpha = 0;
4755 }
4756 completion:^(BOOL finished) {
4757 [promotingPanel removeFromSuperview];
4758 }];
4759 };
4760
4761 [UIView animateWithDuration:ios::material::kDuration3
4762 animations:transition
4763 completion:completion];
4764}
4765
4766- (ContextualSearchPanelView*)createPanelView {
4767 PanelConfiguration* config;
4768 CGSize panelContainerSize = self.view.bounds.size;
4769 if (IsIPadIdiom()) {
4770 config = [PadPanelConfiguration
4771 configurationForContainerSize:panelContainerSize
4772 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4773 } else {
4774 config = [PhonePanelConfiguration
4775 configurationForContainerSize:panelContainerSize
4776 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4777 }
stkhapuginf58b10d02017-04-10 13:36:174778 ContextualSearchPanelView* newPanel =
4779 [[ContextualSearchPanelView alloc] initWithConfiguration:config];
sdefresnee65fd872016-12-19 13:38:134780 [newPanel addMotionObserver:self];
4781 [newPanel addMotionObserver:_contextualSearchMask];
4782 return newPanel;
4783}
4784
4785#pragma mark - ContextualSearchPanelMotionObserver
4786
4787- (void)panel:(ContextualSearchPanelView*)panel
4788 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
4789 // If the header is offset, it's offscreen (or moving offscreen) and the
4790 // toolbar shouldn't be opacity-adjusted by the contextual search panel.
4791 if ([self currentHeaderOffset] != 0)
4792 return;
4793
4794 CGFloat toolbarAlpha;
4795
4796 if (motion.state == ContextualSearch::PREVIEWING) {
4797 // As the panel moves past the previewing position, the toolbar should
4798 // become more transparent.
4799 toolbarAlpha = 1 - motion.gradation;
4800 } else if (motion.state == ContextualSearch::COVERING) {
4801 // The toolbar should be totally transparent when the panel is covering.
4802 toolbarAlpha = 0.0;
4803 } else {
4804 return;
4805 }
4806
4807 // On iPad, the toolbar doesn't go fully transparent, so map |toolbarAlpha|'s
4808 // [0-1.0] range to [0.5-1.0].
4809 if (IsIPadIdiom()) {
4810 toolbarAlpha = 0.5 + (toolbarAlpha * 0.5);
4811 [_tabStripController view].alpha = toolbarAlpha;
4812 }
4813
4814 [self updateToolbarControlsAlpha:toolbarAlpha];
4815 [self updateToolbarBackgroundAlpha:toolbarAlpha];
4816}
4817
4818- (void)panel:(ContextualSearchPanelView*)panel
4819 didChangeToState:(ContextualSearch::PanelState)toState
4820 fromState:(ContextualSearch::PanelState)fromState {
4821 if (toState == ContextualSearch::DISMISSED) {
4822 // Panel has become hidden.
4823 _infoBarContainer->RestoreInfobars();
4824 [self updateToolbarControlsAlpha:1.0];
4825 [self updateToolbarBackgroundAlpha:1.0];
4826 [_tabStripController view].alpha = 1.0;
4827 } else if (fromState == ContextualSearch::DISMISSED) {
4828 // Panel has become visible.
4829 _infoBarContainer->SuspendInfobars();
4830 }
4831}
4832
4833- (void)panelWillPromote:(ContextualSearchPanelView*)panel {
4834 [panel removeMotionObserver:self];
4835}
4836
4837- (CGFloat)currentHeaderHeight {
4838 return [self headerHeight] - [self currentHeaderOffset];
4839}
4840
4841#pragma mark - InfoBarControllerDelegate
4842
4843- (void)infoBarContainerStateChanged:(bool)isAnimating {
4844 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4845 DCHECK(infoBarContainerView);
4846 CGRect containerFrame = infoBarContainerView.frame;
4847 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4848 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4849 containerFrame.size.height = height;
4850 BOOL isViewVisible = self.visible;
4851 [UIView animateWithDuration:0.1
4852 animations:^{
4853 [infoBarContainerView setFrame:containerFrame];
4854 }
4855 completion:^(BOOL finished) {
4856 if (!isViewVisible)
4857 return;
4858 UIAccessibilityPostNotification(
4859 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4860 }];
4861}
4862
4863- (BOOL)shouldAutorotate {
4864 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4865 // Don't rotate if a voice search is being presented or dismissed. Once the
4866 // transition animations finish, only the Voice Search UIViewController's
4867 // |-shouldAutorotate| will be called.
4868 return NO;
4869 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4870 // Don't auto rotate if side swipe controller view says not to.
4871 return NO;
4872 } else {
4873 return [super shouldAutorotate];
4874 }
4875}
4876
4877// Always return yes, as this tap should work with various recognizers,
4878// including UITextTapRecognizer, UILongPressGestureRecognizer,
4879// UIScrollViewPanGestureRecognizer and others.
4880- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4881 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4882 (UIGestureRecognizer*)otherGestureRecognizer {
4883 return YES;
4884}
4885
4886// Tap gestures should only be recognized within |_contentArea|.
4887- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4888 CGPoint location = [gesture locationInView:self.view];
4889
4890 // Only allow touches on descendant views of |_contentArea|.
4891 UIView* hitView = [self.view hitTest:location withEvent:nil];
4892 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4893}
4894
4895#pragma mark - SideSwipeController Delegate Methods
4896
4897- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4898 DCHECK(!IsIPadIdiom());
4899 // Update frame incase orientation changed while |_contentArea| was out of
4900 // the view hierarchy.
4901 [_contentArea setFrame:[sideSwipeView frame]];
4902
4903 [self.view insertSubview:_contentArea atIndex:0];
4904 [self updateVoiceSearchBarVisibilityAnimated:NO];
4905 [self updateToolbar];
4906
4907 // Reset horizontal stack view.
4908 [sideSwipeView removeFromSuperview];
4909 [_sideSwipeController setInSwipe:NO];
4910 [_infoBarContainer->view() setHidden:NO];
4911}
4912
4913- (UIView*)contentView {
4914 return _contentArea;
4915}
4916
4917- (TabStripController*)tabStripController {
4918 return _tabStripController;
4919}
4920
4921- (WebToolbarController*)toolbarController {
4922 return _toolbarController;
4923}
4924
4925- (BOOL)preventSideSwipe {
4926 if ([_toolbarController toolsPopupController])
4927 return YES;
4928
4929 if (_voiceSearchController && _voiceSearchController->IsVisible())
4930 return YES;
4931
4932 if ([_contextualSearchPanel state] >= ContextualSearch::PEEKING)
4933 return YES;
4934
4935 if (!self.active)
4936 return YES;
4937
4938 return NO;
4939}
4940
4941- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4942 if (visible) {
4943 [self updateVoiceSearchBarVisibilityAnimated:NO];
4944 [self updateToolbar];
4945 [_infoBarContainer->view() setHidden:NO];
4946 } else {
4947 // Hide UI accessories such as find bar and first visit overlays
4948 // for welcome page.
4949 [self hideFindBarWithAnimation:NO];
4950 [_infoBarContainer->view() setHidden:YES];
4951 [_voiceSearchBar setHidden:YES];
4952 }
4953}
4954
4955- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4956 BOOL seenToolbar = NO;
4957 BOOL seenInfoBarContainer = NO;
4958 BOOL seenContentArea = NO;
4959 for (UIView* view in views.subviews) {
4960 if (view == [_toolbarController view])
4961 seenToolbar = YES;
4962 else if (view == _infoBarContainer->view())
4963 seenInfoBarContainer = YES;
4964 else if (view == _contentArea)
4965 seenContentArea = YES;
4966 if ((seenToolbar && !seenInfoBarContainer) ||
4967 (seenInfoBarContainer && !seenContentArea))
4968 return NO;
4969 }
4970 return YES;
4971}
4972
4973#pragma mark - PreloadControllerDelegate methods
4974
4975- (BOOL)shouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444976 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134977}
4978
sdefresnee65fd872016-12-19 13:38:134979#pragma mark - BookmarkBridgeMethods
4980
4981// If an added or removed bookmark is the same as the current url, update the
4982// toolbar so the star highlight is kept in sync.
4983- (void)bookmarkNodeModified:(const BookmarkNode*)node {
kkhorimotob110b262017-06-01 18:38:254984 if ([_model currentTab] &&
4985 node->url() == [_model currentTab].lastCommittedURL) {
sdefresnee65fd872016-12-19 13:38:134986 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:254987 }
sdefresnee65fd872016-12-19 13:38:134988}
4989
4990// If all bookmarks are removed, update the toolbar so the star highlight is
4991// kept in sync.
4992- (void)allBookmarksRemoved {
4993 [self updateToolbar];
4994}
4995
4996#pragma mark - ShareToDelegate methods
4997
4998- (void)shareDidComplete:(ShareTo::ShareResult)shareStatus
jife5fcd332017-03-16 15:14:584999 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:135000 // The shareTo dialog dismisses itself instead of through
5001 // |-dismissViewControllerAnimated:completion:| so we must reset the
5002 // presenting state here.
5003 self.presenting = NO;
5004 [self.dialogPresenter tryToPresent];
5005
5006 switch (shareStatus) {
5007 case ShareTo::SHARE_SUCCESS:
pinkerton07e27842017-03-02 15:29:025008 if ([message length]) {
5009 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:135010 [self showSnackbar:message];
pinkerton07e27842017-03-02 15:29:025011 }
sdefresnee65fd872016-12-19 13:38:135012 break;
5013 case ShareTo::SHARE_ERROR:
5014 [self showErrorAlert:IDS_IOS_SHARE_TO_ERROR_ALERT_TITLE
5015 message:IDS_IOS_SHARE_TO_ERROR_ALERT];
5016 break;
5017 case ShareTo::SHARE_NETWORK_FAILURE:
5018 [self showErrorAlert:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT_TITLE
5019 message:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT];
5020 break;
5021 case ShareTo::SHARE_SIGN_IN_FAILURE:
5022 [self showErrorAlert:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT_TITLE
5023 message:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT];
5024 break;
5025 case ShareTo::SHARE_CANCEL:
5026 case ShareTo::SHARE_UNKNOWN_RESULT:
5027 break;
5028 }
5029}
5030
5031- (void)passwordAppExDidFinish:(ShareTo::ShareResult)shareStatus
5032 username:(NSString*)username
5033 password:(NSString*)password
jife5fcd332017-03-16 15:14:585034 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:135035 switch (shareStatus) {
5036 case ShareTo::SHARE_SUCCESS: {
5037 PasswordController* passwordController =
5038 [[_model currentTab] passwordController];
5039 __block BOOL shown = NO;
5040 [passwordController findAndFillPasswordForms:username
5041 password:password
5042 completionHandler:^(BOOL completed) {
5043 if (shown || !completed || ![message length])
5044 return;
pinkerton07e27842017-03-02 15:29:025045 TriggerHapticFeedbackForNotification(
5046 UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:135047 [self showSnackbar:message];
5048 shown = YES;
5049 }];
5050 break;
5051 }
5052 default:
5053 break;
5054 }
5055}
5056
5057- (void)showErrorAlert:(int)titleMessageId message:(int)messageId {
5058 NSString* title = l10n_util::GetNSString(titleMessageId);
5059 NSString* message = l10n_util::GetNSString(messageId);
5060 [self showErrorAlertWithStringTitle:title message:message];
5061}
5062
5063- (void)showErrorAlertWithStringTitle:(NSString*)title
5064 message:(NSString*)message {
5065 // Dismiss current alert.
5066 [_alertCoordinator stop];
5067
stkhapuginc9eee7b2017-04-10 15:49:275068 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5069 message:message
5070 viewController:self];
sdefresnee65fd872016-12-19 13:38:135071 [_alertCoordinator start];
5072}
5073
5074- (void)showSnackbar:(NSString*)message {
5075 [_dependencyFactory showSnackbarWithMessage:message];
5076}
5077
5078#pragma mark - Show Mail Composer methods
5079
5080- (void)showMailComposer:(id)sender {
5081 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
5082 if (![MFMailComposeViewController canSendMail]) {
5083 NSString* alertTitle =
5084 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
5085 NSString* alertMessage =
5086 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
5087 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5088 return;
5089 }
stkhapuginc9eee7b2017-04-10 15:49:275090 MFMailComposeViewController* mailViewController =
5091 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135092 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
5093 [mailViewController setToRecipients:[command toRecipients]];
5094 [mailViewController setSubject:[command subject]];
5095 [mailViewController setMessageBody:[command body] isHTML:NO];
5096
5097 const base::FilePath& textFile = [command textFileToAttach];
5098 if (!textFile.empty()) {
5099 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5100 NSData* data = [NSData dataWithContentsOfFile:filename];
5101 if (data) {
5102 NSString* displayName =
5103 base::SysUTF8ToNSString(textFile.BaseName().value());
5104 [mailViewController addAttachmentData:data
5105 mimeType:@"text/plain"
5106 fileName:displayName];
5107 }
5108 }
5109
5110 [mailViewController setMailComposeDelegate:self];
5111 [self presentViewController:mailViewController animated:YES completion:nil];
5112}
5113
5114#pragma mark - MFMailComposeViewControllerDelegate methods
5115
5116- (void)mailComposeController:(MFMailComposeViewController*)controller
5117 didFinishWithResult:(MFMailComposeResult)result
5118 error:(NSError*)error {
5119 [self dismissViewControllerAnimated:YES completion:nil];
5120}
5121
5122#pragma mark - StoreKitLauncher methods
5123
5124- (void)productViewControllerDidFinish:
5125 (SKStoreProductViewController*)viewController {
5126 [self dismissViewControllerAnimated:YES completion:nil];
5127}
5128
5129- (void)openAppStore:(NSString*)appId {
5130 if (![appId length])
5131 return;
5132 NSDictionary* product =
5133 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275134 SKStoreProductViewController* storeViewController =
5135 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135136 [storeViewController setDelegate:self];
5137 [storeViewController loadProductWithParameters:product completionBlock:nil];
5138 [self presentViewController:storeViewController animated:YES completion:nil];
5139}
5140
5141#pragma mark - TabDialogDelegate methods
5142
sdefresnee65fd872016-12-19 13:38:135143- (void)cancelDialogForTab:(Tab*)tab {
5144 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5145}
5146
5147#pragma mark - FKFeedbackPromptDelegate methods
5148
5149- (void)userTappedRateApp:(UIView*)view {
5150 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275151 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135152}
5153
5154- (void)userTappedSendFeedback:(UIView*)view {
5155 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275156 _rateThisAppDialog = nil;
5157 GenericChromeCommand* command =
5158 [[GenericChromeCommand alloc] initWithTag:IDC_REPORT_AN_ISSUE];
sdefresnee65fd872016-12-19 13:38:135159 [self chromeExecuteCommand:command];
5160}
5161
5162- (void)userTappedDismiss:(UIView*)view {
5163 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275164 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135165}
5166
5167#pragma mark - VoiceSearchBarDelegate
5168
5169- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275170 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135171 [self ensureVoiceSearchControllerCreated];
5172 return _voiceSearchController->IsTextToSpeechEnabled() &&
5173 _voiceSearchController->IsTextToSpeechSupported();
5174}
5175
5176- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275177 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135178 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5179}
5180
5181#pragma mark - VoiceSearchPresenter
5182
5183- (UIView*)voiceSearchButton {
5184 return _voiceSearchButton;
5185}
5186
5187- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5188 return [self currentLogoAnimationControllerOwner];
5189}
5190
5191@end