blob: e7e1255ecbb11694fc2b2c1c61e546bf0f9740c0 [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;
peterlaurens90ac0d32017-06-08 21:13:39575// A block to be run when the |tabWasAdded:| method completes the animation
576// for the presentation of a new tab. Can be used to record performance metrics.
577@property(nonatomic, strong, nullable)
578 ProceduralBlock foregroundTabWasAddedCompletionBlock;
sdefresnee65fd872016-12-19 13:38:13579
liaoyukeea9f3ee62017-03-07 22:05:39580// The user agent type used to load the currently visible page. User agent type
581// is NONE if there is no visible page or visible page is a native page.
582@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
583
stkhapugin952ecef2017-04-11 12:11:45584// Returns the header views, all the chrome on top of the page, including the
585// ones that cannot be scrolled off screen by full screen.
586@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
587
sdefresnee65fd872016-12-19 13:38:13588// BVC initialization:
589// If the BVC is initialized with a valid browser state & tab model immediately,
590// the path is straightforward: functionality is enabled, and the UI is built
591// when -viewDidLoad is called.
592// If the BVC is initialized without a browser state or tab model, the tab model
593// and browser state may or may not be provided before -viewDidLoad is called.
594// In most cases, they will not, to improve startup performance.
595// In order to handle this, initialization of various aspects of BVC have been
596// broken out into the following functions, which have expectations (enforced
597// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
598
599// Registers for notifications.
600- (void)registerForNotifications;
601// Called when a tab is starting to load. If it's a link click or form
602// submission, the user is navigating away from any entries in the forward
603// history. Tell the toolbar so it can update the UI appropriately.
604// See the warning on [Tab webWillStartLoadingURL] about invocation of this
605// method sequence by malicious pages.
606- (void)pageLoadStarting:(NSNotification*)notify;
607// Called when a tab actually starts loading.
608- (void)pageLoadStarted:(NSNotification*)notify;
609// Called when a tab finishes loading. Update the Omnibox with the url and
610// stop any page load progess display.
611- (void)pageLoadComplete:(NSNotification*)notify;
612// Called when a tab is deselected in the model.
613// This notification also occurs when a tab is closed.
614- (void)tabDeselected:(NSNotification*)notify;
615// Animates sliding current tab and rotate-entering new tab while new tab loads
616// in background on the iPhone only.
617- (void)tabWasAdded:(NSNotification*)notify;
618
619// Updates non-view-related functionality with the given browser state and tab
620// model.
621// Does not matter whether or not the view has been loaded.
622- (void)updateWithTabModel:(TabModel*)model
623 browserState:(ios::ChromeBrowserState*)browserState;
624// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
625// the status bar to mimic this layout.
626- (void)installFakeStatusBar;
627// Builds the UI parts of tab strip and the toolbar. Does not matter whether
628// or not browser state and tab model are valid.
629- (void)buildToolbarAndTabStrip;
630// Updates view-related functionality with the given tab model and browser
631// state. The view must have been loaded. Uses |_browserState| and |_model|.
632- (void)addUIFunctionalityForModelAndBrowserState;
633// Sets the correct frame and heirarchy for subviews and helper views.
634- (void)setUpViewLayout;
635// Sets the correct frame for the tab strip based on the given maximum width.
636- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
637// Makes |tab| the currently visible tab, displaying its view. Calls
638// -selectedTabChanged on the toolbar only if |newSelection| is YES.
639- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
640// Initializes the bookmark interaction controller if not already initialized.
641- (void)initializeBookmarkInteractionController;
642
sdefresnee65fd872016-12-19 13:38:13643// Shows the tools menu popup.
644- (void)showToolsMenuPopup;
645// Add all delegates to the provided |tab|.
646- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14647// Remove delegates from the provided |tab|.
648- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13649// Closes the current tab, with animation if applicable.
650- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13651// Shows the menu to initiate sharing |data|.
652- (void)sharePageWithData:(ShareToData*)data;
653// Convenience method to share the current page.
654- (void)sharePage;
655// Prints the web page in the current tab.
656- (void)print;
657// Shows the Online Help Page in a tab.
658- (void)showHelpPage;
659// Show the bookmarks page.
660- (void)showAllBookmarks;
661// Shows a panel within the New Tab Page.
662- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel;
663// Shows the "rate this app" dialog.
664- (void)showRateThisAppDialog;
665// Dismisses the "rate this app" dialog.
666- (void)dismissRateThisAppDialog;
667#if !defined(NDEBUG)
668// Shows the source of the current page.
669- (void)viewSource;
670#endif
olivierrobin889af53f2017-03-01 14:56:32671// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13672- (BOOL)isTabNativePage:(Tab*)tab;
673// Returns the view to use when animating a page in or out, positioning it to
674// fill the content area but not actually adding it to the view hierarchy.
675- (UIImageView*)pageOpenCloseAnimationView;
676// Returns the view to use when animating full screen NTP paper in, filling the
677// entire screen but not actually adding it to the view hierarchy.
678- (UIImageView*)pageFullScreenOpenCloseAnimationView;
679// Updates the toolbar display based on the current tab.
680- (void)updateToolbar;
681// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12682// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13683- (void)updateDialogPresenterActiveState;
684// Dismisses popups and modal dialogs that are displayed above the BVC upon size
685// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
686// is performed.
687// TODO(crbug.com/522721): Support size changes for all popups and modal
688// dialogs.
689- (void)dismissPopups;
690// Create and show the find bar.
691- (void)initFindBarForTab;
692// Search for find bar query string.
693- (void)searchFindInPage;
694// Update find bar with model data. If |shouldFocus| is set to YES, the text
695// field will become first responder.
696- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
697// Close and disable find in page bar.
698- (void)closeFindInPage;
699// Hide find bar.
700- (void)hideFindBarWithAnimation:(BOOL)animate;
701// Shows find bar. If |selectText| is YES, all text inside the Find Bar
702// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
703// set to be first responder.
704- (void)showFindBarWithAnimation:(BOOL)animate
705 selectText:(BOOL)selectText
706 shouldFocus:(BOOL)shouldFocus;
707// Show the Page Security Info.
708- (void)showPageInfoPopupForView:(UIView*)sourceView;
709// Hide the Page Security Info.
710- (void)hidePageInfoPopupForView:(UIView*)sourceView;
711// Shows the tab history popup containing the tab's backward history.
712- (void)showTabHistoryPopupForBackwardHistory;
713// Shows the tab history popup containing the tab's forward history.
714- (void)showTabHistoryPopupForForwardHistory;
715// Navigate back/forward to the selected entry in the tab's history.
716- (void)navigateToSelectedEntry:(id)sender;
717// The infobar state (typically height) has changed.
718- (void)infoBarContainerStateChanged:(bool)is_animating;
719// Adds a CardView on top of the contentArea either taking the size of the full
720// screen or just the size of the space under the header.
721// Returns the CardView that was added.
722- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
723// Called when either a tab finishes loading or when a tab with finished content
724// is added directly to the model via pre-rendering. The tab must be non-nil and
725// must be a member of the tab model controlled by this BrowserViewController.
726- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
727// Evaluates Javascript asynchronously using the current page context.
728- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13729// Helper methods used by ShareToDelegate methods.
730// Shows an alert with the given title and message id.
731- (void)showErrorAlert:(int)titleMessageId message:(int)messageId;
732// Helper method displaying an alert with the given title and message.
733// Dismisses previous alert if it has not been dismissed yet.
734- (void)showErrorAlertWithStringTitle:(NSString*)title
735 message:(NSString*)message;
736// Shows a self-dismissing snackbar displaying |message|.
737- (void)showSnackbar:(NSString*)message;
738// Induces an intentional crash in the browser process.
739- (void)induceBrowserCrash;
740// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17741- (void)managePermissionAndSaveImage:(NSData*)data
742 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13743// Saves the image. In order to keep the metadata of the image, the image is
744// saved as a temporary file on disk then saved in photos.
745// This should be called on FILE thread.
gambard9efce7a2017-02-09 18:53:17746- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13747// Called when Chrome has been denied access to the photos or videos and the
748// user can change it.
749// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
750// settings. Dismiss previous alert if it has not been dismissed yet.
751- (void)displayImageErrorAlertWithSettingsOnMainQueue;
752// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
753// previous alert if it has not been dismissed yet.
754- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
755// Called when Chrome has been denied access to the photos or videos and the
756// user cannot change it.
757// Shows a privacy alert on the main queue, with errorContent as the message.
758// Dismisses previous alert if it has not been dismissed yet.
759- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
760// Called with the results of saving a picture in the photo album. If error is
761// nil the save succeeded.
762- (void)finishSavingImageWithError:(NSError*)error;
763// Provides a view that encompasses currently displayed infobar(s) or nil
764// if no infobar is presented.
765- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
766// Returns a vertical infobar offset relative to the tab content.
767- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
768// Provides a view that encompasses the voice search bar if it's displayed or
769// nil if the voice search bar isn't displayed.
770- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
771// Returns a vertical voice search bar offset relative to the tab content.
772- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
773// Lazily instantiates |_voiceSearchController|.
774- (void)ensureVoiceSearchControllerCreated;
775// Lazily instantiates |_voiceSearchBar| and adds it to the view.
776- (void)ensureVoiceSearchBarCreated;
777// Shows/hides the voice search bar.
778- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
779// The LogoAnimationControllerOwner to be used for the next logo transition
780// animation.
781- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13782// Returns the footer view if one exists (e.g. the voice search bar).
783- (UIView*)footerView;
784// Returns the height of the header view for the tab model's current tab.
785- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13786// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45787- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13788 atOffset:(CGFloat)headerOffset;
789// Returns the y coordinate for the footer's frame when animating the footer
790// in/out of fullscreen.
791- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
792// Called when the animation for setting the header view's offset is finished.
793// |completed| should indicate if the animation finished completely or was
794// interrupted. |offset| should indicate the header offset after the animation.
795// |dragged| should indicate if the header moved due to the user dragging.
796- (void)fullScreenController:(FullScreenController*)controller
797 headerAnimationCompleted:(BOOL)completed
798 offset:(CGFloat)offset;
799// Performs a search with the image at the given url. The referrer is used to
800// download the image.
801- (void)searchByImageAtURL:(const GURL&)url
802 referrer:(const web::Referrer)referrer;
803// Saves the image at the given URL on the system's album. The referrer is used
804// to download the image.
805- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
806
807// Determines the center of |sender| if it's a view or a toolbar item, and save
808// the CGPoint and timestamp.
809- (void)setLastTapPoint:(id)sender;
810// Get return the last stored |_lastTapPoint| if it's been set within the past
811// second.
812- (CGPoint)lastTapPoint;
813// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
814- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
815// Returns the native controller being used by |tab|'s web controller.
816- (id)nativeControllerForTab:(Tab*)tab;
817// Installs the BVC as overscroll actions controller of |nativeContent| if
818// needed. Sets the style of the overscroll actions toolbar.
819- (void)setOverScrollActionControllerToStaticNativeContent:
820 (StaticHtmlNativeContent*)nativeContent;
821// Whether the BVC should declare keyboard commands.
822- (BOOL)shouldRegisterKeyboardCommands;
823// Adds the given url to the reading list.
824- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
825@end
826
827class InfoBarContainerDelegateIOS
828 : public infobars::InfoBarContainer::Delegate {
829 public:
830 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
831 : controller_(controller) {}
832
833 ~InfoBarContainerDelegateIOS() override {}
834
835 private:
836 SkColor GetInfoBarSeparatorColor() const override {
837 NOTIMPLEMENTED();
838 return SK_ColorBLACK;
839 }
840
841 int ArrowTargetHeightForInfoBar(
842 size_t index,
843 const gfx::SlideAnimation& animation) const override {
844 return 0;
845 }
846
847 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
848 int arrow_target_height,
849 int bar_target_height,
850 int* arrow_height,
851 int* arrow_half_width,
852 int* bar_height) const override {
853 DCHECK_NE(-1, bar_target_height)
854 << "Infobars don't have a default height on iOS";
855 *arrow_height = 0;
856 *arrow_half_width = 0;
857 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
858 }
859
860 void InfoBarContainerStateChanged(bool is_animating) override {
861 [controller_ infoBarContainerStateChanged:is_animating];
862 }
863
864 bool DrawInfoBarArrows(int* x) const override { return false; }
865
stkhapuginf58b10d02017-04-10 13:36:17866 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13867};
868
869// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
870@interface BrowserViewController (BookmarkBridgeMethods)
871// If a bookmark matching the currentTab url is added or moved, update the
872// toolbar state so the star highlight is in sync.
873- (void)bookmarkNodeModified:(const BookmarkNode*)node;
874- (void)allBookmarksRemoved;
875@end
876
877// Handle notification that bookmarks has been removed changed so we can update
878// the bookmarked star icon.
879class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
880 public:
881 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
882 : owner_(owner) {}
883
884 ~BrowserBookmarkModelBridge() override {}
885
886 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
887 const BookmarkNode* parent,
888 int old_index,
889 const BookmarkNode* node,
890 const std::set<GURL>& removed_urls) override {
891 [owner_ bookmarkNodeModified:node];
892 }
893
894 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
895 bool ids_reassigned) override {}
896
897 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
898 const BookmarkNode* old_parent,
899 int old_index,
900 const BookmarkNode* new_parent,
901 int new_index) override {}
902
903 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
904 const BookmarkNode* parent,
905 int index) override {
906 [owner_ bookmarkNodeModified:parent->GetChild(index)];
907 }
908
909 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
910 const BookmarkNode* node) override {}
911
912 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
913 const BookmarkNode* node) override {}
914
915 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
916 const BookmarkNode* node) override {}
917
918 void BookmarkAllUserNodesRemoved(
919 bookmarks::BookmarkModel* model,
920 const std::set<GURL>& removed_urls) override {
921 [owner_ allBookmarksRemoved];
922 }
923
924 private:
stkhapuginf58b10d02017-04-10 13:36:17925 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13926};
927
928@implementation BrowserViewController
929
930@synthesize contentArea = _contentArea;
931@synthesize typingShield = _typingShield;
932@synthesize active = _active;
933@synthesize visible = _visible;
934@synthesize viewVisible = _viewVisible;
935@synthesize dismissingModal = _dismissingModal;
936@synthesize hideStatusBar = _hideStatusBar;
937@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
938@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39939@synthesize foregroundTabWasAddedCompletionBlock =
940 _foregroundTabWasAddedCompletionBlock;
sdefresnee65fd872016-12-19 13:38:13941
942#pragma mark - Object lifecycle
943
944- (instancetype)initWithTabModel:(TabModel*)model
945 browserState:(ios::ChromeBrowserState*)browserState
946 dependencyFactory:
947 (BrowserViewControllerDependencyFactory*)factory {
948 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
949 if (self) {
950 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17951
stkhapuginc9eee7b2017-04-10 15:49:27952 _dependencyFactory = factory;
953 _nativeControllersForTabIDs = [NSMapTable strongToWeakObjectsMapTable];
954 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
955 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39956 _dispatcher = [[CommandDispatcher alloc] init];
957 [_dispatcher startDispatchingToTarget:self
958 forProtocol:@protocol(UrlLoader)];
959 [_dispatcher startDispatchingToTarget:self
960 forProtocol:@protocol(WebToolbarDelegate)];
961 [_dispatcher startDispatchingToTarget:self
962 forSelector:@selector(chromeExecuteCommand:)];
963
sdefresnee65fd872016-12-19 13:38:13964 _javaScriptDialogPresenter.reset(
965 new JavaScriptDialogPresenterImpl(_dialogPresenter));
966 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
967 // TODO(leng): Delay this.
968 [[UpgradeCenter sharedInstance] registerClient:self];
969 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13970 if (model && browserState)
971 [self updateWithTabModel:model browserState:browserState];
972 if ([[NSUserDefaults standardUserDefaults]
973 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:27974 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:13975 }
976 }
977 return self;
978}
979
980- (instancetype)initWithNibName:(NSString*)nibNameOrNil
981 bundle:(NSBundle*)nibBundleOrNil {
982 NOTREACHED();
983 return nil;
984}
985
986- (instancetype)initWithCoder:(NSCoder*)aDecoder {
987 NOTREACHED();
988 return nil;
989}
990
991- (void)dealloc {
stkhapuginc9eee7b2017-04-10 15:49:27992 _tabStripController = nil;
993 _infoBarContainer = nil;
994 _readingListMenuNotifier = nil;
sdefresnedc432f42017-01-17 14:36:59995 if (_bookmarkModel)
996 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
sdefresnee65fd872016-12-19 13:38:13997 [_model removeObserver:self];
998 [[UpgradeCenter sharedInstance] unregisterClient:self];
999 [[NSNotificationCenter defaultCenter] removeObserver:self];
1000 [_toolbarController setDelegate:nil];
stkhapuginc9eee7b2017-04-10 15:49:271001 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131002 _voiceSearchController->SetDelegate(nil);
1003 [_rateThisAppDialog setDelegate:nil];
1004 [_model closeAllTabs];
sdefresnee65fd872016-12-19 13:38:131005}
1006
1007#pragma mark - Accessibility
1008
1009- (BOOL)accessibilityPerformEscape {
1010 [self dismissPopups];
1011 return YES;
1012}
1013
1014#pragma mark - Properties
1015
sdefresnee65fd872016-12-19 13:38:131016- (void)setActive:(BOOL)active {
1017 if (_active == active) {
1018 return;
1019 }
1020 _active = active;
1021
1022 // If not active, display an activity indicator overlay over the view to
1023 // prevent interaction with the web page.
1024 // TODO(crbug.com/637093): This coordinator should be managed by the
1025 // coordinator used to present BrowserViewController, when implemented.
1026 if (active) {
1027 [self.activityOverlayCoordinator stop];
1028 self.activityOverlayCoordinator = nil;
1029 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171030 self.activityOverlayCoordinator =
1031 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131032 [self.activityOverlayCoordinator start];
1033 }
1034
1035 if (_browserState) {
1036 web::ActiveStateManager* active_state_manager =
1037 web::BrowserState::GetActiveStateManager(_browserState);
1038 active_state_manager->SetActive(active);
1039 }
1040
1041 [_model setWebUsageEnabled:active];
1042 [self updateDialogPresenterActiveState];
1043
1044 if (active) {
1045 // Make sure the tab (if any; it's possible to get here without a current
1046 // tab if the caller is about to create one) ends up on screen completely.
1047 Tab* currentTab = [_model currentTab];
1048 // Force loading the view in case it was not loaded yet.
1049 [self ensureViewCreated];
1050 if (_expectingForegroundTab)
1051 [currentTab.webController setOverlayPreviewMode:YES];
1052 if (currentTab)
1053 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341054 } else {
1055 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131056 }
1057 [_contextualSearchController enableContextualSearch:active];
1058 [_paymentRequestManager enablePaymentRequest:active];
1059
1060 [self setNeedsStatusBarAppearanceUpdate];
1061}
1062
1063- (void)setPrimary:(BOOL)primary {
1064 [_model setPrimary:primary];
1065 if (primary) {
1066 [self updateDialogPresenterActiveState];
1067 } else {
1068 self.dialogPresenter.active = false;
1069 }
1070}
1071
1072- (BOOL)isPlayingTTS {
1073 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1074}
1075
sdefresne6165c8742017-01-16 15:42:021076- (ios::ChromeBrowserState*)browserState {
1077 return _browserState;
1078}
1079
1080- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271081 return _model;
sdefresne6165c8742017-01-16 15:42:021082}
1083
sdefresnee65fd872016-12-19 13:38:131084- (SideSwipeController*)sideSwipeController {
1085 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271086 _sideSwipeController =
1087 [[SideSwipeController alloc] initWithTabModel:_model
1088 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131089 [_sideSwipeController setSnapshotDelegate:self];
1090 [_sideSwipeController setSwipeDelegate:self];
1091 }
1092 return _sideSwipeController;
1093}
1094
1095- (PreloadController*)preloadController {
stkhapuginc9eee7b2017-04-10 15:49:271096 return _preloadController;
sdefresnee65fd872016-12-19 13:38:131097}
1098
1099- (DialogPresenter*)dialogPresenter {
1100 return _dialogPresenter;
1101}
1102
1103- (BOOL)canUseReaderMode {
1104 Tab* tab = [_model currentTab];
1105 if ([self isTabNativePage:tab])
1106 return NO;
1107
1108 return [tab canSwitchToReaderMode];
1109}
1110
1111- (BOOL)canUseDesktopUserAgent {
1112 Tab* tab = [_model currentTab];
1113 if ([self isTabNativePage:tab])
1114 return NO;
1115
1116 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441117 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131118}
1119
1120// Whether the sharing menu should be shown.
1121- (BOOL)canShowShareMenu {
kkhorimotob110b262017-06-01 18:38:251122 const GURL& URL = [_model currentTab].lastCommittedURL;
1123 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131124}
1125
1126- (BOOL)canShowFindBar {
1127 // Make sure web controller can handle find in page.
1128 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421129 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131130 return NO;
rohitrao005a6432017-03-16 20:52:421131 }
sdefresnee65fd872016-12-19 13:38:131132
rohitrao005a6432017-03-16 20:52:421133 auto* helper = FindTabHelper::FromWebState(tab.webState);
1134 return (helper && helper->CurrentPageSupportsFindInPage() &&
1135 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131136}
1137
liaoyukeea9f3ee62017-03-07 22:05:391138- (web::UserAgentType)userAgentType {
1139 web::WebState* webState = [_model currentTab].webState;
1140 if (!webState)
1141 return web::UserAgentType::NONE;
1142 web::NavigationItem* visibleItem =
1143 webState->GetNavigationManager()->GetVisibleItem();
1144 if (!visibleItem)
1145 return web::UserAgentType::NONE;
1146
1147 return visibleItem->GetUserAgentType();
1148}
1149
sdefresnee65fd872016-12-19 13:38:131150- (void)setVisible:(BOOL)visible {
1151 if (_visible == visible)
1152 return;
1153 _visible = visible;
1154}
1155
1156- (void)setViewVisible:(BOOL)viewVisible {
1157 if (_viewVisible == viewVisible)
1158 return;
1159 _viewVisible = viewVisible;
1160 self.visible = viewVisible;
1161 [self updateDialogPresenterActiveState];
1162}
1163
1164- (BOOL)isToolbarOnScreen {
1165 return [self headerHeight] - [self currentHeaderOffset] > 0;
1166}
1167
kkhorimotoa44349c12017-04-12 23:02:121168- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1169 if (_inNewTabAnimation == inNewTabAnimation)
1170 return;
1171 _inNewTabAnimation = inNewTabAnimation;
1172 [self updateDialogPresenterActiveState];
1173}
1174
sdefresnee65fd872016-12-19 13:38:131175- (BOOL)isInNewTabAnimation {
1176 return _inNewTabAnimation;
1177}
1178
1179- (BOOL)shouldShowVoiceSearchBar {
1180 // On iPads, the voice search bar should only be shown for regular horizontal
1181 // size class configurations. It should always be shown for voice search
1182 // results Tabs on iPhones, including configurations with regular horizontal
1183 // size classes (i.e. landscape iPhone 6 Plus).
1184 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1185 UIUserInterfaceSizeClassCompact;
1186 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1187 (!IsIPadIdiom() || compactWidth);
1188}
1189
1190- (void)setHideStatusBar:(BOOL)hideStatusBar {
1191 if (_hideStatusBar == hideStatusBar)
1192 return;
1193 _hideStatusBar = hideStatusBar;
1194 [self setNeedsStatusBarAppearanceUpdate];
1195}
1196
1197#pragma mark - IBActions
1198
1199- (void)shieldWasTapped:(id)sender {
1200 [_toolbarController cancelOmniboxEdit];
1201}
1202
1203- (void)newTab:(id)sender {
peterlaurens90ac0d32017-06-08 21:13:391204 // Observe the timing of the new tab creation, both MainController
1205 // and BrowserViewController call into this method on the correct BVC to
1206 // create new tabs making it preferable to doing this in
1207 // |chromeExecuteCommand:|.
1208 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
1209 BOOL offTheRecord = self.isOffTheRecord;
1210 self.foregroundTabWasAddedCompletionBlock = ^{
1211 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
1212 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
1213 if (offTheRecord) {
1214 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
1215 timeDelta);
1216 } else {
1217 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
1218 }
1219 };
1220
sdefresnee65fd872016-12-19 13:38:131221 [self setLastTapPoint:sender];
1222 DCHECK(self.visible || self.dismissingModal);
1223 Tab* currentTab = [_model currentTab];
1224 if (currentTab) {
jif7fed8122017-02-08 13:15:251225 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
sdefresnee65fd872016-12-19 13:38:131226 }
1227 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1228 transition:ui::PAGE_TRANSITION_TYPED];
1229}
1230
1231#pragma mark - UIViewController methods
1232
1233// Perform additional set up after loading the view, typically from a nib.
1234- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281235 CGRect initialViewsRect = self.view.frame;
1236 initialViewsRect.origin.y += StatusBarHeight();
1237 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131238 UIViewAutoresizing initialViewAutoresizing =
1239 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1240
stkhapuginf58b10d02017-04-10 13:36:171241 self.contentArea =
1242 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131243 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171244 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131245 self.typingShield.autoresizingMask = initialViewAutoresizing;
1246 [self.typingShield addTarget:self
1247 action:@selector(shieldWasTapped:)
1248 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131249 self.view.autoresizingMask = initialViewAutoresizing;
1250 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1251 [self.view addSubview:self.contentArea];
1252 [self.view addSubview:self.typingShield];
1253 [super viewDidLoad];
1254
1255 // Install fake status bar for iPad iOS7
1256 [self installFakeStatusBar];
1257 [self buildToolbarAndTabStrip];
1258 [self setUpViewLayout];
1259 // If the tab model and browser state are valid, finish initialization.
1260 if (_model && _browserState)
1261 [self addUIFunctionalityForModelAndBrowserState];
1262
1263 // Add a tap gesture recognizer to save the last tap location for the source
1264 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271265 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1266 initWithTarget:self
1267 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131268 [tapRecognizer setDelegate:self];
1269 [tapRecognizer setCancelsTouchesInView:NO];
1270 [_contentArea addGestureRecognizer:tapRecognizer];
1271}
1272
1273- (void)viewDidAppear:(BOOL)animated {
1274 [super viewDidAppear:animated];
1275 self.viewVisible = YES;
1276 [self updateDialogPresenterActiveState];
1277}
1278
1279- (void)viewWillAppear:(BOOL)animated {
1280 [super viewWillAppear:animated];
1281
1282 // Reparent the toolbar if it's been relinquished.
1283 if (_isToolbarControllerRelinquished)
1284 [self reparentToolbarController];
1285
1286 self.visible = YES;
1287
1288 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251289 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131290 _infoBarContainer->RestoreInfobars();
1291 }
1292
1293 // If the controller is suspended, or has been paged out due to low memory,
1294 // updating the view will be handled when it's displayed again.
1295 if (![_model webUsageEnabled] || !self.contentArea)
1296 return;
1297 // Update the displayed tab (if any; the switcher may not have created one
1298 // yet) in case it changed while showing the switcher.
1299 Tab* currentTab = [_model currentTab];
1300 if (currentTab)
1301 [self displayTab:currentTab isNewSelection:YES];
1302}
1303
1304- (void)viewWillDisappear:(BOOL)animated {
1305 self.viewVisible = NO;
1306 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131307 [[_model currentTab] wasHidden];
1308 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251309 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131310 _infoBarContainer->SuspendInfobars();
1311 }
1312 [super viewWillDisappear:animated];
1313}
1314
1315- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1316 duration:(NSTimeInterval)duration {
1317 [super willRotateToInterfaceOrientation:orient duration:duration];
1318 [self dismissPopups];
1319 [self reshowFindBarIfNeededWithCoordinator:nil];
1320}
1321
1322- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1323 [super didRotateFromInterfaceOrientation:orient];
1324
1325 // This reinitializes the toolbar, including updating the Overlay View,
1326 // if there is one.
1327 [self updateToolbar];
1328 [self infoBarContainerStateChanged:false];
1329}
1330
1331- (BOOL)prefersStatusBarHidden {
1332 return self.hideStatusBar;
1333}
1334
1335// Called when in the foreground and the OS needs more memory. Release as much
1336// as possible.
1337- (void)didReceiveMemoryWarning {
1338 // Releases the view if it doesn't have a superview.
1339 [super didReceiveMemoryWarning];
1340
1341 // Release any cached data, images, etc that aren't in use.
1342 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1343 // but since the FaviconCache uses obj-c in the header, it can't be included
1344 // there.
1345 if (_browserState) {
1346 FaviconLoader* loader =
1347 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1348 _browserState);
1349 if (loader)
1350 loader->PurgeCache();
1351 }
1352
1353 if (![self isViewLoaded]) {
1354 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1355 // as the BrowserViewController.
1356 self.contentArea = nil;
1357 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271358 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131359 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271360 _contentSuggestionsCoordinator = nil;
1361 _qrScannerViewController = nil;
1362 _readingListCoordinator = nil;
1363 _toolbarController = nil;
1364 _toolbarModelDelegate = nil;
1365 _toolbarModelIOS = nil;
1366 _tabStripController = nil;
1367 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131368 }
1369}
1370
1371- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1372 [super traitCollectionDidChange:previousTraitCollection];
1373 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1374 // because in some cases the presented view controller isn't a child of the
1375 // BVC in the view controller hierarchy (some intervening object isn't a
1376 // view controller).
1377 [self.presentedViewController
1378 traitCollectionDidChange:previousTraitCollection];
1379 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1380 // Update voice search bar visibility.
1381 [self updateVoiceSearchBarVisibilityAnimated:NO];
1382}
1383
1384- (void)viewWillTransitionToSize:(CGSize)size
1385 withTransitionCoordinator:
1386 (id<UIViewControllerTransitionCoordinator>)coordinator {
1387 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1388 [self dismissPopups];
1389 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1390}
1391
1392- (void)reshowFindBarIfNeededWithCoordinator:
1393 (id<UIViewControllerTransitionCoordinator>)coordinator {
1394 if (![_findBarController isFindInPageShown])
1395 return;
1396
1397 // Record focused state.
1398 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1399
1400 [self hideFindBarWithAnimation:NO];
1401
stkhapuginc9eee7b2017-04-10 15:49:271402 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131403 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1404 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271405 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131406 if (strongSelf)
1407 [strongSelf showFindBarWithAnimation:NO
1408 selectText:NO
1409 shouldFocus:isFocusedBeforeReshow];
1410 };
1411
1412 BOOL enqueued =
1413 [coordinator animateAlongsideTransition:nil completion:completion];
1414 if (!enqueued) {
1415 completion(nil);
1416 }
1417}
1418
1419- (void)dismissViewControllerAnimated:(BOOL)flag
1420 completion:(void (^)())completion {
1421 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271422 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131423 [super dismissViewControllerAnimated:flag
1424 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271425 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131426 [strongSelf setDismissingModal:NO];
1427 [strongSelf setPresenting:NO];
1428 if (completion)
1429 completion();
1430 [[strongSelf dialogPresenter] tryToPresent];
1431 }];
1432}
1433
1434- (void)presentViewController:(UIViewController*)viewControllerToPresent
1435 animated:(BOOL)flag
1436 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271437 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131438 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1439 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1440 // presented, to show a temporary view of the launch screen and then remove it
1441 // when the controller for the FRE has been presented. This fix should be
1442 // removed when the FRE startup code is rewritten.
1443 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1444 experimental_flags::AlwaysDisplayFirstRun()) &&
1445 !tests_hook::DisableFirstRun();
1446 // These if statements check that |presentViewController| is being called for
1447 // the FRE case.
1448 if (firstRunLaunch &&
1449 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1450 UINavigationController* navController =
1451 base::mac::ObjCCastStrict<UINavigationController>(
1452 viewControllerToPresent);
1453 if ([navController.topViewController
1454 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1455 self.hideStatusBar = YES;
1456
1457 // Load view from Launch Screen and add it to window.
1458 NSBundle* mainBundle = base::mac::FrameworkBundle();
1459 NSArray* topObjects =
1460 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1461 UIViewController* launchScreenController =
1462 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1463 // |launchScreenView| is loaded as an autoreleased object, and is retained
1464 // by the |completion| block below.
1465 UIView* launchScreenView = launchScreenController.view;
1466 launchScreenView.userInteractionEnabled = NO;
1467 launchScreenView.frame = self.view.window.bounds;
1468 [self.view.window addSubview:launchScreenView];
1469
1470 // Replace the completion handler sent to the superclass with one which
1471 // removes |launchScreenView| and resets the status bar. If |completion|
1472 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271473 __weak BrowserViewController* weakSelf = self;
1474 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131475 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271476 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131477 if (completion)
1478 completion();
stkhapuginc9eee7b2017-04-10 15:49:271479 };
sdefresnee65fd872016-12-19 13:38:131480 }
1481 }
1482
1483 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171484 if ([_sideSwipeController inSwipe]) {
1485 [_sideSwipeController resetContentView];
1486 }
sdefresnee65fd872016-12-19 13:38:131487
1488 [super presentViewController:viewControllerToPresent
1489 animated:flag
1490 completion:finalCompletionHandler];
1491}
1492
1493#pragma mark - Notification handling
1494
1495- (void)registerForNotifications {
1496 DCHECK(_model);
1497 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1498 [defaultCenter addObserver:self
1499 selector:@selector(pageLoadStarting:)
1500 name:kTabModelTabWillStartLoadingNotification
1501 object:_model];
1502 [defaultCenter addObserver:self
1503 selector:@selector(pageLoadStarted:)
1504 name:kTabModelTabDidStartLoadingNotification
1505 object:_model];
1506 [defaultCenter addObserver:self
1507 selector:@selector(pageLoadComplete:)
1508 name:kTabModelTabDidFinishLoadingNotification
1509 object:_model];
1510 [defaultCenter addObserver:self
1511 selector:@selector(tabDeselected:)
1512 name:kTabModelTabDeselectedNotification
1513 object:_model];
1514 [defaultCenter addObserver:self
1515 selector:@selector(tabWasAdded:)
1516 name:kTabModelNewTabWillOpenNotification
1517 object:_model];
1518}
1519
1520- (void)pageLoadStarting:(NSNotification*)notify {
1521 Tab* tab = notify.userInfo[kTabModelTabKey];
1522 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511523
1524 // Stop any Find in Page searches and close the find bar when navigating to a
1525 // new page.
1526 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361527
sdefresnee65fd872016-12-19 13:38:131528 if (tab == [_model currentTab]) {
1529 // TODO(pinkerton): Fill in here about hiding the forward button on
1530 // navigation.
1531 }
1532}
1533
1534- (void)pageLoadStarted:(NSNotification*)notify {
1535 Tab* tab = notify.userInfo[kTabModelTabKey];
1536 DCHECK(tab);
1537 if (tab == [_model currentTab]) {
1538 if (![self isTabNativePage:tab]) {
1539 [_toolbarController currentPageLoadStarted];
1540 }
1541 [self updateVoiceSearchBarVisibilityAnimated:NO];
1542 }
1543}
1544
1545- (void)pageLoadComplete:(NSNotification*)notify {
1546 // Update the UI, but only if the current tab.
1547 Tab* tab = notify.userInfo[kTabModelTabKey];
1548 if (tab == [_model currentTab]) {
1549 // There isn't any need to update the toolbar here. When the page finishes,
1550 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1551 }
1552
1553 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1554
1555 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1556}
1557
1558- (void)tabDeselected:(NSNotification*)notify {
1559 DCHECK(notify);
1560 Tab* tab = notify.userInfo[kTabModelTabKey];
1561 DCHECK(tab);
1562 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221563 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131564}
1565
1566- (void)tabWasAdded:(NSNotification*)notify {
1567 Tab* tab = notify.userInfo[kTabModelTabKey];
1568 DCHECK(tab);
1569
1570 // Update map if a native controller was vended before the tab was added.
1571 id<CRWNativeContent> nativeController =
1572 [_nativeControllersForTabIDs objectForKey:kNativeControllerTemporaryKey];
1573 if (nativeController) {
1574 [_nativeControllersForTabIDs
1575 removeObjectForKey:kNativeControllerTemporaryKey];
1576 [_nativeControllersForTabIDs setObject:nativeController forKey:tab.tabId];
1577 }
1578
1579 // When adding new tabs, check what kind of reminder infobar should
1580 // be added to the new tab. Try to add only one of them.
1581 // This check is done when a new tab is added either through the Tools Menu
1582 // "New Tab" or through "New Tab" in Stack View Controller. This method
1583 // is called after a new tab has added and finished initial navigation.
1584 // If this is added earlier, the initial navigation may end up clearing
1585 // the infobar(s) that are just added. See http://crbug/340250 for details.
1586 [[UpgradeCenter sharedInstance] addInfoBarToManager:[tab infoBarManager]
1587 forTabId:[tab tabId]];
1588 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1589 ios_internal::sync::displaySyncErrors(_browserState, tab);
1590 }
1591
1592 // The rest of this function initiates the new tab animation, which is
1593 // phone-specific.
1594 if (IsIPadIdiom())
1595 return;
1596
1597 // Do nothing if browsing is currently suspended. The BVC will set everything
1598 // up correctly when browsing resumes.
1599 if (!self.visible || ![_model webUsageEnabled])
1600 return;
1601
1602 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1603
1604 // Block that starts voice search at the end of new Tab animation if
1605 // necessary.
1606 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1607 if (_startVoiceSearchAfterNewTabAnimation) {
1608 _startVoiceSearchAfterNewTabAnimation = NO;
1609 [self startVoiceSearch];
1610 }
1611 };
1612
kkhorimotoa44349c12017-04-12 23:02:121613 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131614 if (!inBackground) {
1615 UIView* animationParentView = _contentArea;
1616 // Create the new page image, and load with the new tab page snapshot.
1617 CGFloat newPageOffset = 0;
1618 UIImageView* newPage;
kkhorimotob110b262017-06-01 18:38:251619 if (tab.lastCommittedURL == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
sdefresnee65fd872016-12-19 13:38:131620 !IsIPadIdiom()) {
1621 animationParentView = self.view;
1622 newPage = [self pageFullScreenOpenCloseAnimationView];
1623 } else {
1624 newPage = [self pageOpenCloseAnimationView];
1625 }
1626 newPageOffset = newPage.frame.origin.y;
1627
1628 [tab view].frame = _contentArea.bounds;
1629 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1630 [animationParentView addSubview:newPage];
1631 CGPoint origin = [self lastTapPoint];
1632 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1633 newPage, -newPageOffset,
1634 newPage.frame.size.height - newPage.image.size.height, origin,
1635 _isOffTheRecord, NULL, ^{
1636 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121637 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421638 // Use the model's currentTab here because it is possible that it can
1639 // be reset to a new value before the new Tab animation finished (e.g.
1640 // if another Tab shows a dialog via |dialogPresenter|). However, that
1641 // tab's view hasn't been displayed yet because it was in a new tab
1642 // animation.
1643 Tab* currentTab = [_model currentTab];
1644 if (currentTab) {
1645 [self tabSelected:currentTab];
1646 }
sdefresnee65fd872016-12-19 13:38:131647 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391648
1649 if (self.foregroundTabWasAddedCompletionBlock) {
1650 self.foregroundTabWasAddedCompletionBlock();
1651 }
sdefresnee65fd872016-12-19 13:38:131652 });
1653 } else {
1654 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1655 // snapshot before adding the views needed for the background animation.
1656 Tab* topTab = [_model currentTab];
1657 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1658 visibleFrameOnly:self.isToolbarOnScreen];
1659 // Add three layers in order on top of the contentArea for the animation:
1660 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271661 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131662 InstallBackgroundInView(background);
1663 [_contentArea addSubview:background];
1664
1665 // 2. A CardView displaying the data from the current tab.
1666 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1667 NSString* title = [topTab title];
1668 if (![title length])
1669 title = [topTab urlDisplayString];
1670 [topCard setTitle:title];
1671 [topCard setFavicon:[topTab favicon]];
1672 [topCard setImage:image];
1673
1674 // 3. A new, blank CardView to represent the new tab being added.
1675 // Launch the new background tab animation.
1676 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1677 topCard, [_contentArea frame], IsPortrait(), ^{
1678 [background removeFromSuperview];
1679 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121680 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131681 // Resnapshot the top card if it has its own toolbar, as the toolbar
1682 // will be captured in the new tab animation, but isn't desired for
1683 // the stack view snapshots.
1684 id nativeController = [self nativeControllerForTab:topTab];
1685 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1686 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1687 startVoiceSearchIfNecessaryBlock();
1688 });
1689 }
peterlaurens90ac0d32017-06-08 21:13:391690 // Reset the foreground tab completion block so that it can never be
1691 // called more than once regardless of foreground/background tab appearances.
1692 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131693}
1694
1695#pragma mark - UI Configuration and Layout
1696
1697- (void)updateWithTabModel:(TabModel*)model
1698 browserState:(ios::ChromeBrowserState*)browserState {
1699 DCHECK(model);
1700 DCHECK(browserState);
1701 DCHECK(!_model);
1702 DCHECK(!_browserState);
1703 _browserState = browserState;
1704 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271705 _model = model;
sdefresnee65fd872016-12-19 13:38:131706 [_model addObserver:self];
1707
1708 if (!_isOffTheRecord) {
1709 [DefaultIOSWebViewFactory
1710 registerWebViewFactory:[ChromeWebViewFactory class]];
1711 }
1712 NSUInteger count = [_model count];
1713 for (NSUInteger index = 0; index < count; ++index)
1714 [self installDelegatesForTab:[_model tabAtIndex:index]];
1715
1716 [self registerForNotifications];
1717
gambardbdc07cc2017-02-03 16:43:111718 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
1719 _browserState->GetRequestContext(), web::WebThread::GetBlockingPool());
stkhapuginc9eee7b2017-04-10 15:49:271720 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131721
sdefresnedc432f42017-01-17 14:36:591722 // Register for bookmark changed notification (BookmarkModel may be null
1723 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131724 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591725 if (_bookmarkModel) {
1726 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1727 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1728 }
sdefresnee65fd872016-12-19 13:38:131729}
1730
1731- (void)ensureViewCreated {
1732 ignore_result([self view]);
1733}
1734
1735- (void)browserStateDestroyed {
1736 [self setActive:NO];
1737 // Reset the toolbar opacity in case it was changed for contextual search.
1738 [self updateToolbarControlsAlpha:1.0];
1739 [self updateToolbarBackgroundAlpha:1.0];
1740 [_contextualSearchController close];
stkhapuginc9eee7b2017-04-10 15:49:271741 _contextualSearchController = nil;
sdefresnee65fd872016-12-19 13:38:131742 [_contextualSearchPanel removeFromSuperview];
1743 [_contextualSearchMask removeFromSuperview];
1744 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271745 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131746 [_toolbarController browserStateDestroyed];
1747 [_model browserStateDestroyed];
michaeldobc2f42e2017-01-12 19:04:471748 [_preloadController browserStateDestroyed];
stkhapuginc9eee7b2017-04-10 15:49:271749 _preloadController = nil;
sdefresnee65fd872016-12-19 13:38:131750 // The file remover needs the browser state, so needs to be destroyed now.
stkhapuginc9eee7b2017-04-10 15:49:271751 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131752 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391753 [_dispatcher stopDispatchingToTarget:self];
1754 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131755}
1756
1757- (void)installFakeStatusBar {
1758 if (IsIPadIdiom()) {
1759 CGFloat statusBarHeight = StatusBarHeight();
1760 CGRect statusBarFrame =
1761 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271762 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131763 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1764 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1765 [statusBarView layer].zPosition = 99;
1766 [[self view] addSubview:statusBarView];
1767 }
1768
1769 // Add a white bar on phone so that the status bar on the NTP is white.
1770 if (!IsIPadIdiom()) {
1771 CGFloat statusBarHeight = StatusBarHeight();
1772 CGRect statusBarFrame =
1773 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271774 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131775 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1776 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1777 [self.view insertSubview:statusBarView atIndex:0];
1778 }
1779}
1780
1781// Create the UI elements. May or may not have valid browser state & tab model.
1782- (void)buildToolbarAndTabStrip {
1783 DCHECK([self isViewLoaded]);
1784 DCHECK(!_toolbarModelDelegate);
1785
1786 // Create the preload controller before the toolbar controller.
1787 if (!_preloadController) {
stkhapuginc9eee7b2017-04-10 15:49:271788 _preloadController = [_dependencyFactory newPreloadController];
sdefresnee65fd872016-12-19 13:38:131789 [_preloadController setDelegate:self];
1790 }
1791
1792 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201793 _toolbarModelDelegate.reset(
1794 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131795 _toolbarModelIOS.reset([_dependencyFactory
1796 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
stkhapuginc9eee7b2017-04-10 15:49:271797 _toolbarController = [_dependencyFactory
sdefresnee65fd872016-12-19 13:38:131798 newWebToolbarControllerWithDelegate:self
1799 urlLoader:self
stkhapuginc9eee7b2017-04-10 15:49:271800 preloadProvider:_preloadController];
justincohen75011c32017-04-28 16:31:391801 [_dispatcher startDispatchingToTarget:_toolbarController
1802 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131803 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271804 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131805 _voiceSearchController->SetDelegate(_toolbarController);
1806
1807 // If needed, create the tabstrip.
1808 if (IsIPadIdiom()) {
stkhapuginc9eee7b2017-04-10 15:49:271809 _tabStripController =
1810 [_dependencyFactory newTabStripControllerWithTabModel:_model];
1811 _tabStripController.fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131812 }
1813
1814 // Create infobar container.
1815 if (!_infoBarContainerDelegate) {
1816 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1817 _infoBarContainer.reset(
1818 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1819 }
1820}
1821
1822// Enable functionality that only makes sense if the views are loaded and
1823// both browser state and tab model are valid.
1824- (void)addUIFunctionalityForModelAndBrowserState {
1825 DCHECK(_browserState);
1826 DCHECK(_model);
1827 DCHECK([self isViewLoaded]);
1828
1829 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1830
1831 infobars::InfoBarManager* infoBarManager =
1832 [[_model currentTab] infoBarManager];
1833 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1834
1835 // Create contextual search views and controller.
1836 if ([TouchToSearchPermissionsMediator isTouchToSearchAvailableOnDevice] &&
1837 !_browserState->IsOffTheRecord()) {
stkhapuginf58b10d02017-04-10 13:36:171838 _contextualSearchMask = [[ContextualSearchMaskView alloc] init];
sdefresnee65fd872016-12-19 13:38:131839 [self.view insertSubview:_contextualSearchMask
1840 belowSubview:[_toolbarController view]];
1841 _contextualSearchPanel = [self createPanelView];
1842 [self.view insertSubview:_contextualSearchPanel
1843 aboveSubview:[_toolbarController view]];
stkhapuginc9eee7b2017-04-10 15:49:271844 _contextualSearchController =
1845 [[ContextualSearchController alloc] initWithBrowserState:_browserState
1846 delegate:self];
sdefresnee65fd872016-12-19 13:38:131847 [_contextualSearchController setPanel:_contextualSearchPanel];
1848 [_contextualSearchController setTab:[_model currentTab]];
1849 }
1850
1851 if (experimental_flags::IsPaymentRequestEnabled()) {
stkhapuginc9eee7b2017-04-10 15:49:271852 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131853 initWithBaseViewController:self
stkhapuginc9eee7b2017-04-10 15:49:271854 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131855 [_paymentRequestManager setWebState:[_model currentTab].webState];
1856 }
1857}
1858
1859// Set the frame for the various views. View must be loaded.
1860- (void)setUpViewLayout {
1861 DCHECK([self isViewLoaded]);
1862
1863 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1864
1865 CGFloat minY = [self headerOffset];
1866
1867 // If needed, position the tabstrip.
1868 if (IsIPadIdiom()) {
1869 [self layoutTabStripForWidth:widthOfView];
1870 [[self view] addSubview:[_tabStripController view]];
1871 minY += CGRectGetHeight([[_tabStripController view] frame]);
1872 }
1873
1874 // Position the toolbar next, either at the top of the browser view or
1875 // directly under the tabstrip.
1876 CGRect toolbarFrame = [[_toolbarController view] frame];
1877 toolbarFrame.origin = CGPointMake(0, minY);
1878 toolbarFrame.size.width = widthOfView;
1879 [[_toolbarController view] setFrame:toolbarFrame];
1880
1881 // Place the infobar container above the content area.
1882 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1883 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1884
1885 // Place the toolbar controller above the infobar container.
1886 [[self view] insertSubview:[_toolbarController view]
1887 aboveSubview:infoBarContainerView];
1888 minY += CGRectGetHeight(toolbarFrame);
1889
1890 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1891 // content slightly.
1892 minY -= [ToolbarController toolbarDropShadowHeight];
1893
1894 // Adjust the content area to be under the toolbar, for fullscreen or below
1895 // the toolbar is not fullscreen.
1896 CGRect contentFrame = [_contentArea frame];
1897 CGFloat marginWithHeader = StatusBarHeight();
1898 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1899 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1900 contentFrame.origin.y = overlap;
1901 [_contentArea setFrame:contentFrame];
1902
1903 // Adjust the infobar container to be either at the bottom of the screen
1904 // (iPhone) or on the lower toolbar edge (iPad).
1905 CGRect infoBarFrame = contentFrame;
1906 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1907 infoBarFrame.size.height = 0;
1908 [infoBarContainerView setFrame:infoBarFrame];
1909
1910 // Attach the typing shield to the content area but have it hidden.
1911 [_typingShield setFrame:[_contentArea frame]];
1912 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1913 [_typingShield setHidden:YES];
1914 _typingShield.accessibilityIdentifier = @"Typing Shield";
1915 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1916}
1917
1918- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1919 UIView* tabStripView = [_tabStripController view];
1920 CGRect tabStripFrame = [tabStripView frame];
1921 tabStripFrame.origin = CGPointZero;
1922 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1923 // because the CGPointZero above will break reset the offset, but it's not
1924 // clear what removing that will do.
1925 tabStripFrame.origin.y = [self headerOffset];
1926 tabStripFrame.size.width = maxWidth;
1927 [tabStripView setFrame:tabStripFrame];
1928}
1929
1930- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1931 DCHECK(tab);
1932 // Ensure that self.view is loaded to avoid errors that can otherwise occur
1933 // when accessing |_contentArea| below.
1934 if (!_contentArea)
1935 [self ensureViewCreated];
1936
1937 DCHECK(_contentArea);
kkhorimotoa44349c12017-04-12 23:02:121938 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:131939 // Hide findbar. |updateToolbar| will restore the findbar later.
1940 [self hideFindBarWithAnimation:NO];
1941
1942 // Make new content visible, resizing it first as the orientation may
1943 // have changed from the last time it was displayed.
1944 [[tab view] setFrame:_contentArea.bounds];
1945 [_contentArea displayContentView:[tab view]];
1946 }
1947 [self updateToolbar];
1948
1949 if (newSelection)
1950 [_toolbarController selectedTabChanged];
1951
1952 // Notify the Tab that it was displayed.
1953 [tab wasShown];
1954}
1955
1956- (void)initializeBookmarkInteractionController {
1957 if (_bookmarkInteractionController)
1958 return;
stkhapuginc9eee7b2017-04-10 15:49:271959 _bookmarkInteractionController =
1960 [[BookmarkInteractionController alloc] initWithBrowserState:_browserState
1961 loader:self
1962 parentController:self];
sdefresnee65fd872016-12-19 13:38:131963}
1964
1965// Update the state of back and forward buttons, hiding the forward button if
1966// there is nowhere to go. Assumes the model's current tab is up to date.
1967- (void)updateToolbar {
1968 // If the BVC has been partially torn down for low memory, wait for the
1969 // view rebuild to handle toolbar updates.
1970 if (!(_toolbarModelIOS && _browserState))
1971 return;
1972
1973 Tab* tab = [_model currentTab];
1974 if (![tab navigationManager])
1975 return;
1976 [_toolbarController updateToolbarState];
1977 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1978
1979 if (tab.isPrerenderTab && !_toolbarModelIOS->IsLoading())
1980 [_toolbarController showPrerenderingAnimation];
1981
1982 // Also update the loading state for the tools menu (that is really an
1983 // extension of the toolbar on the iPhone).
1984 if (!IsIPadIdiom())
1985 [[_toolbarController toolsPopupController]
1986 setIsTabLoading:_toolbarModelIOS->IsLoading()];
1987
rohitrao005a6432017-03-16 20:52:421988 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
1989 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:131990 [self showFindBarWithAnimation:NO
1991 selectText:YES
1992 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:361993 }
sdefresnee65fd872016-12-19 13:38:131994
1995 // Hide the toolbar if displaying phone NTP.
1996 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:551997 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:131998 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:551999 if (item) {
2000 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132001 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2002 hideToolbar = isNTP && !_isOffTheRecord &&
2003 ![_toolbarController isOmniboxFirstResponder] &&
2004 ![_toolbarController showingOmniboxPopup];
2005 }
2006 [[_toolbarController view] setHidden:hideToolbar];
2007 }
2008}
2009
2010- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122011 self.dialogPresenter.active =
2012 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132013}
2014
2015- (void)dismissPopups {
jif7fed8122017-02-08 13:15:252016 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:132017 [self hidePageInfoPopupForView:nil];
2018 [_toolbarController dismissTabHistoryPopup];
sdefresnee65fd872016-12-19 13:38:132019}
2020
2021#pragma mark - Tap handling
2022
2023- (void)setLastTapPoint:(id)sender {
2024 CGPoint center;
2025 UIView* parentView = nil;
2026 if ([sender isKindOfClass:[UIView class]]) {
2027 center = [sender center];
2028 parentView = [sender superview];
2029 }
2030 if ([sender isKindOfClass:[ToolsMenuViewItem class]]) {
2031 parentView = [[sender tableViewCell] superview];
2032 center = [[sender tableViewCell] center];
2033 }
2034
2035 if (parentView) {
2036 _lastTapPoint = [parentView convertPoint:center toView:self.view];
2037 _lastTapTime = CACurrentMediaTime();
2038 }
2039}
2040
2041- (CGPoint)lastTapPoint {
2042 if (CACurrentMediaTime() - _lastTapTime < 1) {
2043 return _lastTapPoint;
2044 }
2045 return CGPointZero;
2046}
2047
2048- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2049 UIView* view = gestureRecognizer.view;
2050 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2051 _lastTapPoint =
2052 [[view superview] convertPoint:viewCoordinate toView:self.view];
2053 _lastTapTime = CACurrentMediaTime();
2054}
2055
2056- (BOOL)addTabIfNoTabWithNormalBrowserState {
2057 if (![_model count]) {
2058 if (!_isOffTheRecord) {
2059 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2060 transition:ui::PAGE_TRANSITION_TYPED];
2061 return YES;
2062 }
2063 }
2064 return NO;
2065}
2066
2067#pragma mark - Tab creation and selection
2068
2069// Called when either a tab finishes loading or when a tab with finished content
2070// is added directly to the model via pre-rendering.
2071- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2072 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2073
2074 // Persist the session on a delay.
2075 [_model saveSessionImmediately:NO];
2076}
2077
2078- (Tab*)addSelectedTabWithURL:(const GURL&)url
2079 postData:(TemplateURLRef::PostContent*)postData
2080 transition:(ui::PageTransition)transition {
2081 return [self addSelectedTabWithURL:url
2082 postData:postData
2083 atIndex:[_model count]
2084 transition:transition];
2085}
2086
2087- (Tab*)addSelectedTabWithURL:(const GURL&)url
2088 transition:(ui::PageTransition)transition {
2089 return [self addSelectedTabWithURL:url
2090 atIndex:[_model count]
2091 transition:transition];
2092}
2093
2094- (Tab*)addSelectedTabWithURL:(const GURL&)url
2095 atIndex:(NSUInteger)position
2096 transition:(ui::PageTransition)transition {
2097 return [self addSelectedTabWithURL:url
2098 postData:NULL
2099 atIndex:position
2100 transition:transition];
2101}
2102
2103- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2104 postData:(TemplateURLRef::PostContent*)postData
2105 atIndex:(NSUInteger)position
2106 transition:(ui::PageTransition)transition {
2107 if (position == NSNotFound)
2108 position = [_model count];
2109 DCHECK(position <= [_model count]);
2110
2111 web::NavigationManager::WebLoadParams params(URL);
2112 params.transition_type = transition;
2113 if (postData) {
2114 // Extract the content type and post params from |postData| and add them
2115 // to the load params.
2116 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2117 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2118 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172119 params.post_data.reset(data);
2120 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132121 }
sdefresnea6395912017-03-01 01:14:352122 Tab* tab = [_model insertTabWithLoadParams:params
2123 opener:nil
2124 openedByDOM:NO
2125 atIndex:position
2126 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132127 return tab;
2128}
2129
olivierrobin889af53f2017-03-01 14:56:322130// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132131- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322132 web::WebState* webState = tab.webState;
2133 if (!webState)
2134 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392135 web::NavigationItem* visibleItem =
2136 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322137 if (!visibleItem)
2138 return NO;
2139 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132140}
2141
2142- (void)expectNewForegroundTab {
2143 _expectingForegroundTab = YES;
2144}
2145
2146- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2147 CGRect viewBounds, remainder;
2148 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2149 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172150 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132151}
2152
2153- (UIImageView*)pageOpenCloseAnimationView {
2154 CGRect frame = [_contentArea bounds];
2155
2156 frame.size.height = frame.size.height - [self headerHeight];
2157 frame.origin.y = [self headerHeight];
2158
stkhapuginf58b10d02017-04-10 13:36:172159 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132160 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2161 pageView.center = center;
2162
2163 pageView.backgroundColor = [UIColor whiteColor];
2164 return pageView;
2165}
2166
2167- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142168 // Unregistration happens when the Tab is removed from the TabModel.
sdefresnee65fd872016-12-19 13:38:132169 tab.dialogDelegate = self;
2170 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132171 tab.passKitDialogProvider = self;
2172 tab.fullScreenControllerDelegate = self;
2173 if (!IsIPadIdiom()) {
2174 tab.overscrollActionsControllerDelegate = self;
2175 }
olivierrobin9ce77b82017-01-12 17:29:192176 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132177 tab.tabSnapshottingDelegate = self;
2178 // Install the proper CRWWebController delegates.
2179 tab.webController.nativeProvider = self;
2180 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512181 // BrowserViewController presents SKStoreKitViewController on behalf of a
2182 // tab.
2183 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2184 if (tabHelper)
2185 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132186 tab.webState->SetDelegate(_webStateDelegate.get());
2187}
2188
sdefresne49cf2862017-03-15 13:46:142189- (void)uninstallDelegatesForTab:(Tab*)tab {
2190 tab.dialogDelegate = nil;
2191 tab.snapshotOverlayProvider = nil;
2192 tab.passKitDialogProvider = nil;
2193 tab.fullScreenControllerDelegate = nil;
2194 if (!IsIPadIdiom()) {
2195 tab.overscrollActionsControllerDelegate = nil;
2196 }
2197 tab.tabHeadersDelegate = nil;
2198 tab.tabSnapshottingDelegate = nil;
2199 tab.webController.nativeProvider = nil;
2200 tab.webController.swipeRecognizerProvider = nil;
2201 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2202 if (tabHelper)
2203 tabHelper->SetLauncher(nil);
2204 tab.webState->SetDelegate(nullptr);
2205}
2206
sdefresnee65fd872016-12-19 13:38:132207// Called when a tab is selected in the model. Make any required view changes.
2208// The notification will not be sent when the tab is already the selected tab.
2209- (void)tabSelected:(Tab*)tab {
2210 DCHECK(tab);
2211
2212 // Ignore changes while the tab stack view is visible (or while suspended).
2213 // The display will be refreshed when this view becomes active again.
2214 if (!self.visible || ![_model webUsageEnabled])
2215 return;
2216
2217 [self displayTab:tab isNewSelection:YES];
2218
kkhorimotoa44349c12017-04-12 23:02:122219 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132220 // Now that the new tab has been displayed, return to normal. Rather than
2221 // keep a reference to the previous tab, just turn off preview mode for all
2222 // tabs (since doing so is a no-op for the tabs that don't have it set).
2223 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272224 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132225 [tab.webController setOverlayPreviewMode:NO];
2226 }
2227 }
2228}
2229
2230#pragma mark - External files
2231
2232- (NSSet*)referencedExternalFiles {
2233 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2234
2235 // TODO(noyau): this is incorrect, the caller should know that the model is
2236 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592237 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132238 return filesReferencedByTabs;
2239
2240 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2241 _bookmarkModel->GetBookmarks(&bookmarks);
2242 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2243 for (const auto& bookmark : bookmarks) {
2244 GURL bookmarkUrl = bookmark.url;
2245 if (UrlIsExternalFileReference(bookmarkUrl)) {
2246 [bookmarkedFiles
2247 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2248 }
2249 }
2250 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2251}
2252
2253- (void)removeExternalFilesImmediately:(BOOL)immediately
2254 completionHandler:(ProceduralBlock)completionHandler {
2255 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2256 DCHECK(!_isOffTheRecord);
2257 _externalFileRemover.reset(new ExternalFileRemover(self));
2258 // Delay the cleanup of the unreferenced files received from other apps
2259 // to not impact startup performance.
2260 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2261 _externalFileRemover->RemoveAfterDelay(
2262 base::TimeDelta::FromSeconds(delay),
stkhapuginf58b10d02017-04-10 13:36:172263 base::BindBlockArc(completionHandler ? completionHandler
2264 : ^{
2265 }));
sdefresnee65fd872016-12-19 13:38:132266}
2267
2268#pragma mark - SnapshotOverlayProvider methods
2269
2270- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2271 NSMutableArray* overlays = [NSMutableArray array];
2272 if (![_model webUsageEnabled]) {
2273 return overlays;
2274 }
2275 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2276 if (voiceSearchView) {
2277 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272278 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132279 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272280 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132281 [overlays addObject:voiceSearchOverlay];
2282 }
2283 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2284 if (infoBarView) {
2285 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272286 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132287 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272288 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132289 [overlays addObject:infoBarOverlay];
2290 }
2291 return overlays;
2292}
2293
2294#pragma mark -
2295
2296- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2297 if (IsIPadIdiom()) {
2298 // Not using overlays on iPad because the content is pushed down by
2299 // infobar and the transition between snapshot and fresh page can
2300 // cause both snapshot and real infobars to appear at the same time.
2301 return nil;
2302 }
2303 Tab* currentTab = [_model currentTab];
2304 if (tab && tab == currentTab) {
2305 infobars::InfoBarManager* infoBarManager = [currentTab infoBarManager];
2306 if (infoBarManager->infobar_count() > 0) {
2307 DCHECK(_infoBarContainer);
2308 return _infoBarContainer->view();
2309 }
2310 }
2311 return nil;
2312}
2313
2314- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272315 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132316 // There is no UI representation for non-current tabs or there is
2317 // no _infoBarContainer instantiated yet.
2318 // Return offset outside of tab.
2319 return CGRectGetMaxY(self.view.frame);
2320 } else if (IsIPadIdiom()) {
2321 // The infobars on iPad are display at the top of a tab.
2322 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2323 } else {
2324 // The infobars on iPhone are displayed at the bottom of a tab.
2325 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2326 return CGRectGetMaxY(visibleFrame) -
2327 CGRectGetHeight(_infoBarContainer->view().frame);
2328 }
2329}
2330
2331- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2332 Tab* currentTab = [_model currentTab];
2333 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2334 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2335 return _voiceSearchBar;
2336 }
2337 return nil;
2338}
2339
2340- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2341 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2342 // There is no UI representation for non-current tabs or there is
2343 // no visible voice search. Return offset outside of tab.
2344 return CGRectGetMaxY(self.view.frame);
2345 } else {
2346 // The voice search bar on iPhone is displayed at the bottom of a tab.
2347 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2348 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2349 }
2350}
2351
2352- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272353 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132354 VoiceSearchProvider* provider =
2355 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2356 if (provider) {
2357 _voiceSearchController =
2358 provider->CreateVoiceSearchController(_browserState);
2359 _voiceSearchController->SetDelegate(_toolbarController);
2360 }
2361 }
2362}
2363
2364- (void)ensureVoiceSearchBarCreated {
2365 if (_voiceSearchBar)
2366 return;
2367
2368 CGFloat width = CGRectGetWidth([[self view] bounds]);
2369 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2370 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272371 _voiceSearchBar = ios::GetChromeBrowserProvider()
2372 ->GetVoiceSearchProvider()
2373 ->BuildVoiceSearchBar(frame);
sdefresnee65fd872016-12-19 13:38:132374 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2375 [_voiceSearchBar setHidden:YES];
2376 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2377 UIViewAutoresizingFlexibleWidth];
2378 [self.view insertSubview:_voiceSearchBar
2379 belowSubview:_infoBarContainer->view()];
2380}
2381
2382- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2383 // Voice search bar exists and is shown/hidden.
2384 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272385 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132386 return;
2387
2388 // Voice search bar doesn't exist and thus is not visible.
2389 if (!_voiceSearchBar && !show)
2390 return;
2391
2392 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272393 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132394 else
stkhapuginc9eee7b2017-04-10 15:49:272395 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132396}
2397
2398- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2399 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2400 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2401 self.shouldShowVoiceSearchBar) {
2402 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2403 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272404 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132405 }
2406 id currentNativeController =
2407 [self nativeControllerForTab:self.tabModel.currentTab];
2408 Protocol* possibleOwnerProtocol =
2409 @protocol(LogoAnimationControllerOwnerOwner);
2410 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2411 [currentNativeController logoAnimationControllerOwner]) {
2412 // If the current native controller is showing a GLIF view (e.g. the NTP
2413 // when there is no doodle), use that GLIFControllerOwner.
2414 return [currentNativeController logoAnimationControllerOwner];
2415 }
2416 return nil;
2417}
2418
2419#pragma mark - PassKitDialogProvider methods
2420
2421- (void)presentPassKitDialog:(NSData*)data {
2422 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272423 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132424 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272425 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132426 if (error || !data) {
2427 if ([_model currentTab]) {
2428 infobars::InfoBarManager* infoBarManager =
2429 [[_model currentTab] infoBarManager];
2430 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2431 // NULL, replace if with DCHECK).
2432 if (infoBarManager)
2433 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2434 }
2435 } else {
2436 PKAddPassesViewController* passKitViewController =
2437 [_dependencyFactory newPassKitViewControllerForPass:pass];
2438 if (passKitViewController) {
2439 [self presentViewController:passKitViewController
2440 animated:YES
2441 completion:^{
2442 }];
2443 }
2444 }
2445}
2446
2447- (UIStatusBarStyle)preferredStatusBarStyle {
2448 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2449 : UIStatusBarStyleDefault;
2450}
2451
2452#pragma mark - CRWWebStateDelegate methods.
2453
eugenebut75a06fa72017-01-09 17:09:552454- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512455 createNewWebStateForURL:(const GURL&)URL
2456 openerURL:(const GURL&)openerURL
2457 initiatedByUser:(BOOL)initiatedByUser {
2458 // Check if requested web state is a popup and block it if necessary.
2459 if (!initiatedByUser) {
2460 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2461 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102462 // It's possible for a page to inject a popup into a window created via
2463 // window.open before its initial load is committed. Rather than relying
2464 // on the last committed or pending NavigationItem's referrer policy, just
2465 // use ReferrerPolicyDefault.
2466 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2467 // policy once referrer policies are correctly recorded in
2468 // NavigationItems.
2469 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512470 helper->HandlePopup(URL, referrer);
2471 return nil;
2472 }
2473 }
2474
2475 // Requested web state should not be blocked from opening.
2476 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2477 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2478
2479 // Tabs open by DOM are always renderer initiated.
2480 web::NavigationManager::WebLoadParams params(GURL{});
2481 params.transition_type = ui::PAGE_TRANSITION_LINK;
2482 params.is_renderer_initiated = true;
2483 Tab* childTab = [[self tabModel]
2484 insertTabWithLoadParams:params
2485 opener:currentTab
2486 openedByDOM:YES
2487 atIndex:TabModelConstants::kTabPositionAutomatically
2488 inBackground:NO];
2489 return childTab.webState;
2490}
2491
eugenebutb46b2122017-03-14 02:43:262492- (void)closeWebState:(web::WebState*)webState {
2493 // Only allow a web page to close itself if it was opened by DOM, or if there
2494 // are no navigation items.
2495 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582496 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262497
2498 if (![self tabModel])
2499 return;
2500
2501 NSUInteger index = [[self tabModel] indexOfTab:tab];
2502 if (index != NSNotFound)
2503 [[self tabModel] closeTabAtIndex:index];
2504}
2505
eugenebut275f5892017-03-09 22:20:512506- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552507 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2508 switch (params.disposition) {
2509 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2510 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2511 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352512 insertTabWithURL:params.url
2513 referrer:params.referrer
2514 transition:params.transition
2515 opener:LegacyTabHelper::GetTabForWebState(webState)
2516 openedByDOM:NO
2517 atIndex:TabModelConstants::kTabPositionAutomatically
2518 inBackground:(params.disposition ==
2519 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552520 return tab.webState;
2521 }
2522 case WindowOpenDisposition::CURRENT_TAB: {
2523 web::NavigationManager::WebLoadParams loadParams(params.url);
2524 loadParams.referrer = params.referrer;
2525 loadParams.transition_type = params.transition;
2526 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2527 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2528 return webState;
2529 }
eugenebutd0984e82017-02-22 23:47:512530 case WindowOpenDisposition::NEW_POPUP: {
2531 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352532 insertTabWithURL:params.url
2533 referrer:params.referrer
2534 transition:params.transition
2535 opener:LegacyTabHelper::GetTabForWebState(webState)
2536 openedByDOM:YES
2537 atIndex:TabModelConstants::kTabPositionAutomatically
2538 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512539 return tab.webState;
2540 }
eugenebut75a06fa72017-01-09 17:09:552541 default:
2542 NOTIMPLEMENTED();
2543 return nullptr;
2544 };
2545}
2546
sdefresnee65fd872016-12-19 13:38:132547- (BOOL)webState:(web::WebState*)webState
2548 handleContextMenu:(const web::ContextMenuParams&)params {
2549 // Prevent context menu from displaying for a tab which is no longer the
2550 // current one.
2551 if (webState != [_model currentTab].webState) {
2552 return NO;
2553 }
2554
2555 // No custom context menu if no valid url is available in |params|.
2556 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
2557 return NO;
2558 }
2559
2560 DCHECK(_browserState);
2561 DCHECK([_model currentTab]);
2562
stkhapuginc9eee7b2017-04-10 15:49:272563 _contextMenuCoordinator =
2564 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2565 params:params];
sdefresnee65fd872016-12-19 13:38:132566
2567 NSString* title = nil;
2568 ProceduralBlock action = nil;
2569
stkhapuginc9eee7b2017-04-10 15:49:272570 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132571 GURL link = params.link_url;
2572 bool isLink = link.is_valid();
2573 GURL imageUrl = params.src_url;
2574 bool isImage = imageUrl.is_valid();
kkhorimotob110b262017-06-01 18:38:252575 const GURL& committedURL = [_model currentTab].lastCommittedURL;
sdefresnee65fd872016-12-19 13:38:132576
2577 if (isLink) {
2578 if (link.SchemeIs(url::kJavaScriptScheme)) {
2579 // Open
2580 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2581 action = ^{
2582 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2583 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2584 };
2585 [_contextMenuCoordinator addItemWithTitle:title action:action];
2586 }
2587
2588 if (web::UrlHasWebScheme(link)) {
kkhorimotob110b262017-06-01 18:38:252589 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132590
sdefresnee65fd872016-12-19 13:38:132591 // Open in New Tab.
2592 title = l10n_util::GetNSStringWithFixup(
2593 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2594 action = ^{
2595 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
2596 [weakSelf webPageOrderedOpen:link
2597 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132598 inBackground:YES
2599 appendTo:kCurrentTab];
2600 };
2601 [_contextMenuCoordinator addItemWithTitle:title action:action];
2602 if (!_isOffTheRecord) {
2603 // Open in Incognito Tab.
2604 title = l10n_util::GetNSStringWithFixup(
2605 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2606 action = ^{
2607 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2608 [weakSelf webPageOrderedOpen:link
2609 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132610 inIncognito:YES
2611 inBackground:NO
2612 appendTo:kCurrentTab];
2613 };
2614 [_contextMenuCoordinator addItemWithTitle:title action:action];
2615 }
olivierrobin51d4cf42017-01-17 13:32:352616 }
gambard65d69152017-03-23 17:44:222617 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352618 NSString* innerText = params.link_text;
2619 if ([innerText length] > 0) {
2620 // Add to reading list.
2621 title = l10n_util::GetNSStringWithFixup(
2622 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2623 action = ^{
2624 Record(ACTION_READ_LATER, isImage, isLink);
2625 [weakSelf addToReadingListURL:link title:innerText];
2626 };
2627 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532628 }
sdefresnee65fd872016-12-19 13:38:132629 }
2630 // Copy Link.
2631 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2632 action = ^{
2633 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282634 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132635 };
2636 [_contextMenuCoordinator addItemWithTitle:title action:action];
2637 }
2638 if (isImage) {
kkhorimotob110b262017-06-01 18:38:252639 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132640 // Save Image.
gambard98b4ddf2017-04-18 07:14:052641 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132642 action = ^{
2643 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2644 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2645 };
2646 [_contextMenuCoordinator addItemWithTitle:title action:action];
2647 // Open Image.
2648 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2649 action = ^{
2650 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2651 [weakSelf loadURL:imageUrl
2652 referrer:referrer
2653 transition:ui::PAGE_TRANSITION_LINK
2654 rendererInitiated:YES];
2655 };
2656 [_contextMenuCoordinator addItemWithTitle:title action:action];
2657 // Open Image In New Tab.
2658 title = l10n_util::GetNSStringWithFixup(
2659 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2660 action = ^{
2661 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2662 [weakSelf webPageOrderedOpen:imageUrl
2663 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132664 inBackground:true
2665 appendTo:kCurrentTab];
2666 };
2667 [_contextMenuCoordinator addItemWithTitle:title action:action];
2668
2669 TemplateURLService* service =
2670 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102671 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132672 if (defaultURL && !defaultURL->image_url().empty() &&
2673 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2674 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2675 defaultURL->short_name());
2676 action = ^{
2677 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2678 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2679 };
2680 [_contextMenuCoordinator addItemWithTitle:title action:action];
2681 }
2682 }
2683
2684 [_contextMenuCoordinator start];
2685 return YES;
2686}
2687
eugenebutb739bdc2017-01-25 06:32:482688- (void)webState:(web::WebState*)webState
2689 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2690 // Display the action sheet with the arrow pointing at the top center of the
2691 // web contents.
sdefresne0452a9d2017-02-09 15:33:282692 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482693 UIView* view = webState->GetView();
2694 CGPoint dialogLocation =
2695 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282696 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032697 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172698 helper->PresentDialog(dialogLocation,
2699 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052700 handler(shouldContinue);
2701 }));
eugenebutb739bdc2017-01-25 06:32:482702}
2703
sdefresnee65fd872016-12-19 13:38:132704- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2705 (web::WebState*)webState {
2706 return _javaScriptDialogPresenter.get();
2707}
2708
eugenebut63232102017-01-19 16:19:402709- (void)webState:(web::WebState*)webState
2710 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2711 proposedCredential:(NSURLCredential*)proposedCredential
2712 completionHandler:(void (^)(NSString* username,
2713 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422714 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2715 if ([tab isPrerenderTab]) {
2716 [tab discardPrerender];
2717 if (handler) {
2718 handler(nil, nil);
2719 }
2720 return;
2721 }
2722
eugenebut63232102017-01-19 16:19:402723 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2724 proposedCredential:proposedCredential
2725 webState:webState
2726 completionHandler:handler];
2727}
2728
sdefresnee65fd872016-12-19 13:38:132729#pragma mark - FullScreenControllerDelegate methods
2730
2731- (CGFloat)headerOffset {
2732 if (IsIPadIdiom())
2733 return StatusBarHeight();
2734 return 0.0;
2735}
2736
stkhapugin952ecef2017-04-11 12:11:452737- (NSArray<HeaderDefinition*>*)headerViews {
2738 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132739 if (![self isViewLoaded])
2740 return results;
2741
2742 if (!IsIPadIdiom()) {
2743 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452744 [results addObject:[HeaderDefinition
2745 definitionWithView:[_toolbarController view]
2746 headerBehaviour:Hideable
2747 heightAdjustment:[ToolbarController
2748 toolbarDropShadowHeight]
2749 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132750 }
2751 } else {
2752 if ([_tabStripController view]) {
stkhapugin952ecef2017-04-11 12:11:452753 [results addObject:[HeaderDefinition
2754 definitionWithView:[_tabStripController view]
2755 headerBehaviour:Hideable
2756 heightAdjustment:0.0
2757 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132758 }
2759 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452760 [results addObject:[HeaderDefinition
2761 definitionWithView:[_toolbarController view]
2762 headerBehaviour:Hideable
2763 heightAdjustment:[ToolbarController
2764 toolbarDropShadowHeight]
2765 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132766 }
2767 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452768 [results addObject:[HeaderDefinition
2769 definitionWithView:[_findBarController view]
2770 headerBehaviour:Overlap
2771 heightAdjustment:0.0
2772 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:132773 }
2774 }
stkhapugin952ecef2017-04-11 12:11:452775 return [results copy];
sdefresnee65fd872016-12-19 13:38:132776}
2777
2778- (UIView*)footerView {
2779 return _voiceSearchBar;
2780}
2781
2782- (CGFloat)headerHeight {
2783 return [self headerHeightForTab:[_model currentTab]];
2784}
2785
2786- (CGFloat)headerHeightForTab:(Tab*)tab {
2787 id nativeController = [self nativeControllerForTab:tab];
2788 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2789 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2790 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2791 // On iPhone, don't add any header height for ToolbarOwner native
2792 // controllers when they're displaying their own toolbar.
2793 return 0;
2794 }
2795
stkhapugin952ecef2017-04-11 12:11:452796 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132797
2798 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452799 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:132800 if (header.view && header.behaviour == Hideable) {
2801 height += CGRectGetHeight([header.view frame]) -
2802 header.heightAdjustement - header.inset;
2803 }
2804 }
2805
2806 return height - StatusBarHeight();
2807}
2808
2809- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:192810 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:132811}
2812
2813- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:452814 NSArray<HeaderDefinition*>* headers = [self headerViews];
2815 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:132816 return 0.0;
2817
2818 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
2819 // API documentation.
2820 if ([[[self tabModel] currentTab] isPrerenderTab])
2821 return [self headerHeight];
2822
2823 UIView* topHeader = headers[0].view;
2824 return -(topHeader.frame.origin.y - [self headerOffset]);
2825}
2826
2827- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
2828 UIView* footer = [self footerView];
2829 CGFloat headerHeight = [self headerHeight];
2830 if (!footer || headerHeight == 0)
2831 return 0.0;
2832
2833 CGFloat footerHeight = CGRectGetHeight(footer.frame);
2834 CGFloat offset = headerOffset * footerHeight / headerHeight;
2835 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
2836}
2837
2838- (void)fullScreenController:(FullScreenController*)controller
2839 headerAnimationCompleted:(BOOL)completed
2840 offset:(CGFloat)offset {
2841 if (completed)
justincohen04c27772016-12-21 20:16:592842 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:132843}
2844
stkhapugin952ecef2017-04-11 12:11:452845- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:132846 atOffset:(CGFloat)headerOffset {
2847 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452848 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:132849 CGRect frame = [header.view frame];
2850 frame.origin.y = height - headerOffset - header.inset;
2851 [header.view setFrame:frame];
2852 if (header.behaviour != Overlap)
2853 height += CGRectGetHeight(frame);
2854 }
2855}
2856
2857- (void)fullScreenController:(FullScreenController*)fullScreenController
2858 drawHeaderViewFromOffset:(CGFloat)headerOffset
2859 animate:(BOOL)animate {
2860 if ([_sideSwipeController inSwipe])
2861 return;
2862
2863 CGRect footerFrame = CGRectZero;
2864 UIView* footer = nil;
2865 // Only animate the voice search bar if the tab is a voice search results tab.
2866 if ([_model currentTab].isVoiceSearchResultsTab) {
2867 footer = [self footerView];
2868 footerFrame = footer.frame;
2869 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2870 }
2871
stkhapugin952ecef2017-04-11 12:11:452872 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132873 void (^block)(void) = ^{
2874 [self setFramesForHeaders:headers atOffset:headerOffset];
2875 footer.frame = footerFrame;
2876 };
2877 void (^completion)(BOOL) = ^(BOOL finished) {
2878 [self fullScreenController:fullScreenController
2879 headerAnimationCompleted:finished
2880 offset:headerOffset];
2881 };
2882 if (animate) {
2883 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2884 delay:0.0
2885 options:UIViewAnimationOptionBeginFromCurrentState
2886 animations:block
2887 completion:completion];
2888 } else {
2889 block();
2890 completion(YES);
2891 }
2892}
2893
2894- (void)fullScreenController:(FullScreenController*)fullScreenController
2895 drawHeaderViewFromOffset:(CGFloat)headerOffset
2896 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
2897 changeTopContentPadding:(BOOL)changeTopContentPadding
2898 scrollingToOffset:(CGFloat)contentOffset {
2899 DCHECK(webViewProxy);
2900 if ([_sideSwipeController inSwipe])
2901 return;
2902
2903 CGRect footerFrame;
2904 UIView* footer = nil;
2905 // Only animate the voice search bar if the tab is a voice search results tab.
2906 if ([_model currentTab].isVoiceSearchResultsTab) {
2907 footer = [self footerView];
2908 footerFrame = footer.frame;
2909 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2910 }
2911
stkhapugin952ecef2017-04-11 12:11:452912 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132913 void (^block)(void) = ^{
2914 [self setFramesForHeaders:headers atOffset:headerOffset];
2915 footer.frame = footerFrame;
2916 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
2917 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
2918 if (changeTopContentPadding)
2919 webViewProxy.topContentPadding = contentOffset;
2920 };
2921 void (^completion)(BOOL) = ^(BOOL finished) {
2922 [self fullScreenController:fullScreenController
2923 headerAnimationCompleted:finished
2924 offset:headerOffset];
2925 };
2926
2927 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2928 delay:0.0
2929 options:UIViewAnimationOptionBeginFromCurrentState
2930 animations:block
2931 completion:completion];
2932}
2933
2934#pragma mark - VoiceSearchBarOwner
2935
2936- (id<VoiceSearchBar>)voiceSearchBar {
2937 return _voiceSearchBar;
2938}
2939
2940#pragma mark - Install OverScrollActionController method.
2941- (void)setOverScrollActionControllerToStaticNativeContent:
2942 (StaticHtmlNativeContent*)nativeContent {
2943 if (!IsIPadIdiom() && !FirstRun::IsChromeFirstRun()) {
2944 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:172945 [[OverscrollActionsController alloc]
2946 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:132947 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:052948 OverscrollStyle style = _isOffTheRecord
2949 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2950 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:132951 controller.style = style;
2952 nativeContent.overscrollActionsController = controller;
2953 }
2954}
2955
2956#pragma mark - OverscrollActionsControllerDelegate methods.
2957
2958- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:052959 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:132960 switch (action) {
rohitrao922b7111c2017-01-03 14:31:052961 case OverscrollAction::NEW_TAB:
sdefresnee65fd872016-12-19 13:38:132962 [self newTab:nil];
2963 break;
rohitrao922b7111c2017-01-03 14:31:052964 case OverscrollAction::CLOSE_TAB:
sdefresnee65fd872016-12-19 13:38:132965 [self closeCurrentTab];
2966 break;
liaoyuke563dc4a2017-03-17 18:36:292967 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:132968 if ([[[_model currentTab] webController] loadPhase] ==
2969 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:232970 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:132971 }
liaoyuke563dc4a2017-03-17 18:36:292972
2973 web::WebState* webState = [_model currentTab].webState;
2974 if (webState)
2975 // |check_for_repost| is true because the reload is explicitly initiated
2976 // by the user.
2977 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
2978 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:132979 break;
liaoyuke563dc4a2017-03-17 18:36:292980 }
rohitrao922b7111c2017-01-03 14:31:052981 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:132982 NOTREACHED();
2983 break;
2984 }
2985}
2986
2987- (BOOL)shouldAllowOverscrollActions {
2988 return YES;
2989}
2990
2991- (UIView*)headerView {
2992 return [_toolbarController view];
2993}
2994
2995- (UIView*)toolbarSnapshotView {
2996 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
2997}
2998
2999- (CGFloat)overscrollActionsControllerHeaderInset:
3000 (OverscrollActionsController*)controller {
3001 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3002 return [self headerHeight];
3003 else
3004 return 0;
3005}
3006
3007- (CGFloat)overscrollHeaderHeight {
3008 return [self headerHeight] + StatusBarHeight();
3009}
3010
3011#pragma mark - TabSnapshottingDelegate methods.
3012
3013- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3014 CGRect pageContentArea = _contentArea.bounds;
3015 if ([_model webUsageEnabled])
3016 pageContentArea = tab.view.bounds;
3017 CGFloat headerHeight = [self headerHeightForTab:tab];
3018 id nativeController = [self nativeControllerForTab:tab];
3019 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3020 headerHeight += [nativeController toolbarHeight];
3021 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3022 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3023}
3024
3025#pragma mark - NewTabPageObserver methods.
3026
3027- (void)selectedPanelDidChange {
3028 [self updateToolbar];
3029}
3030
3031#pragma mark - CRWNativeContentProvider methods
3032
3033- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3034 withError:(NSError*)error
3035 isPost:(BOOL)isPost {
3036 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173037 [[ErrorPageContent alloc] initWithLoader:self
3038 browserState:self.browserState
3039 url:url
3040 error:error
3041 isPost:isPost
3042 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133043 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3044 return errorPageContent;
3045}
3046
3047- (BOOL)hasControllerForURL:(const GURL&)url {
3048 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453049 if (host == kChromeUIOfflineHost) {
3050 // Only allow offline URL that are fully specified.
3051 return reading_list::IsOfflineURLValid(
3052 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3053 }
sdefresnee65fd872016-12-19 13:38:133054
michaeldo352029b2017-05-10 20:41:383055 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost;
sdefresnee65fd872016-12-19 13:38:133056}
3057
olivierrobind43eecb2017-01-27 20:35:263058- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3059 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133060 DCHECK(url.SchemeIs(kChromeUIScheme));
3061
3062 id<CRWNativeContent> nativeController = nil;
3063 std::string url_host = url.host();
3064 if (url_host == kChromeUINewTabHost || url_host == kChromeUIBookmarksHost) {
3065 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173066 [[NewTabPageController alloc] initWithUrl:url
3067 loader:self
3068 focuser:_toolbarController
3069 ntpObserver:self
3070 browserState:_browserState
3071 colorCache:_dominantColorCache
3072 webToolbarDelegate:self
justincohenbc913632017-04-18 14:41:453073 tabModel:_model
justincohen75011c32017-04-28 16:31:393074 parentViewController:self
3075 dispatcher:_dispatcher];
sdefresnee65fd872016-12-19 13:38:133076 pageController.swipeRecognizerProvider = self.sideSwipeController;
3077
3078 // Panel is always NTP for iPhone.
3079 NewTabPage::PanelIdentifier panelType = NewTabPage::kMostVisitedPanel;
3080
3081 if (IsIPadIdiom()) {
3082 // New Tab Page can have multiple panels. Each panel is addressable
3083 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3084 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3085 // the Bookmark Manager, etc.
3086 // The utility functions NewTabPage::IdentifierFromFragment() and
3087 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3088 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3089 // without changing the URL since the URL may be chrome://bookmarks/#123.
3090 // If the URL is chrome://newtab/, pre-select the panel based on the
3091 // #fragment.
3092 panelType = url_host == kChromeUIBookmarksHost
3093 ? NewTabPage::kBookmarksPanel
3094 : NewTabPage::IdentifierFromFragment(url.ref());
3095 }
3096 [pageController selectPanel:panelType];
3097 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453098 } else if (url_host == kChromeUIOfflineHost &&
3099 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133100 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173101 [[OfflinePageNativeContent alloc] initWithLoader:self
3102 browserState:_browserState
3103 webState:webState
3104 URL:url];
sdefresnee65fd872016-12-19 13:38:133105 [self setOverScrollActionControllerToStaticNativeContent:
3106 staticNativeController];
3107 nativeController = staticNativeController;
3108 } else if (url_host == kChromeUIExternalFileHost) {
3109 // Return an instance of the |ExternalFileController| only if the file is
3110 // still in the sandbox.
3111 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3112 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173113 nativeController =
3114 [[ExternalFileController alloc] initWithURL:url
3115 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133116 }
peterlaurens44615d02017-05-23 20:23:093117 } else if (url_host == kChromeUICrashHost) {
3118 // There is no native controller for kChromeUICrashHost, it is instead
3119 // handled as any other renderer crash by the SadTabTabHelper.
3120 // nativeController must be set to nil to prevent defaulting to a
3121 // PageNotAvailableController.
3122 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133123 } else {
3124 DCHECK(![self hasControllerForURL:url]);
3125 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173126 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133127 }
3128 // If a native controller is vended before its tab is added to the tab model,
3129 // use the temporary key and add it under the new tab's tabId in the
3130 // TabModelObserver callback. This happens:
3131 // - when there is no current tab (occurs when vending the NTP controller for
3132 // the first tab that is opened),
3133 // - when the current tab's url doesn't match |url| (occurs when a native
3134 // controller is opened in a new tab)
3135 // - when the current tab's url matches |url| and there is already a native
3136 // controller of the appropriate type vended to it (occurs when a native
3137 // controller is opened in a new tab from a tab with a matching URL, e.g.
3138 // opening an NTP when an NTP is already displayed in the current tab).
3139 // For normal page loads, history navigations, tab restorations, and crash
3140 // recoveries, the tab will already exist in the tab model and the tabId can
3141 // be used as the native controller key.
3142 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3143 // that native controllers vended here always correspond to the current tab.
3144 Tab* currentTab = [_model currentTab];
3145 NSString* nativeControllerKey = currentTab.tabId;
kkhorimotob110b262017-06-01 18:38:253146 if (!currentTab || currentTab.lastCommittedURL != url ||
sdefresnee65fd872016-12-19 13:38:133147 [[_nativeControllersForTabIDs objectForKey:nativeControllerKey]
3148 isKindOfClass:[nativeController class]]) {
3149 nativeControllerKey = kNativeControllerTemporaryKey;
3150 }
3151 DCHECK(nativeControllerKey);
3152 [_nativeControllersForTabIDs setObject:nativeController
3153 forKey:nativeControllerKey];
3154 return nativeController;
3155}
3156
3157- (id)nativeControllerForTab:(Tab*)tab {
3158 id nativeController = [_nativeControllersForTabIDs objectForKey:tab.tabId];
3159 if (!nativeController) {
3160 // If there is no controller, check for a native controller stored under
3161 // the temporary key.
3162 nativeController = [_nativeControllersForTabIDs
3163 objectForKey:kNativeControllerTemporaryKey];
3164 }
3165 return nativeController;
3166}
3167
3168#pragma mark - DialogPresenterDelegate methods
3169
3170- (void)dialogPresenter:(DialogPresenter*)presenter
3171 willShowDialogForWebState:(web::WebState*)webState {
3172 for (Tab* iteratedTab in self.tabModel) {
3173 if ([iteratedTab webState] == webState) {
3174 self.tabModel.currentTab = iteratedTab;
3175 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3176 break;
3177 }
3178 }
3179}
3180
3181#pragma mark - Context menu methods
3182
3183- (void)searchByImageAtURL:(const GURL&)url
3184 referrer:(const web::Referrer)referrer {
3185 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273186 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113187 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173188 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3189 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113190 DCHECK(data);
3191 dispatch_async(dispatch_get_main_queue(), ^{
3192 [weakSelf searchByImageData:data atURL:image_source_url];
3193 });
3194 };
3195 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133196 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3197 web::PolicyForNavigation(url, referrer));
3198}
3199
3200- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3201 NSData* imageData = data;
3202 UIImage* image = [UIImage imageWithData:imageData];
3203 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3204 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3205 // kSearchByImageMaxImageHeight).
3206 if (image &&
3207 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3208 (image.size.width > kSearchByImageMaxImageWidth ||
3209 image.size.height > kSearchByImageMaxImageHeight)) {
3210 CGSize newImageSize =
3211 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3212 image = [image gtm_imageByResizingToSize:newImageSize
3213 preserveAspectRatio:YES
3214 trimToFit:NO];
3215 imageData = UIImageJPEGRepresentation(image, 1.0);
3216 }
3217
3218 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3219 std::string byteString(bytes, [imageData length]);
3220
3221 TemplateURLService* templateUrlService =
3222 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103223 const TemplateURL* defaultURL =
3224 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133225 DCHECK(!defaultURL->image_url().empty());
3226 DCHECK(defaultURL->image_url_ref().IsValid(
3227 templateUrlService->search_terms_data()));
3228 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3229 search_args.image_url = imageURL;
3230 search_args.image_thumbnail_content = byteString;
3231
3232 // Generate the URL and populate |post_content| with the content type and
3233 // HTTP body for the request.
3234 TemplateURLRef::PostContent post_content;
3235 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3236 search_args, templateUrlService->search_terms_data(), &post_content));
3237 [self addSelectedTabWithURL:result
3238 postData:&post_content
3239 transition:ui::PAGE_TRANSITION_TYPED];
3240}
3241
3242- (void)saveImageAtURL:(const GURL&)url
3243 referrer:(const web::Referrer&)referrer {
3244 DCHECK(url.is_valid());
3245
gambard9efce7a2017-02-09 18:53:173246 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3247 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113248 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133249
gambard9efce7a2017-02-09 18:53:173250 base::FilePath::StringType extension;
3251
3252 bool extensionSuccess =
3253 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3254 if (!extensionSuccess || extension.length() == 0) {
3255 extension = "png";
3256 }
3257
3258 NSString* fileExtension =
3259 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3260 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113261 };
3262 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133263 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3264 web::PolicyForNavigation(url, referrer));
3265}
3266
gambard9efce7a2017-02-09 18:53:173267- (void)managePermissionAndSaveImage:(NSData*)data
3268 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133269 switch ([PHPhotoLibrary authorizationStatus]) {
3270 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173271 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133272 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3273 // Call -saveImage again to check if chrome needs to display an error or
3274 // saves the image.
3275 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173276 [self managePermissionAndSaveImage:data
3277 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133278 }];
3279 break;
stkhapuginf58b10d02017-04-10 13:36:173280 }
sdefresnee65fd872016-12-19 13:38:133281
3282 // The application doesn't have permission to access photo and the user
3283 // cannot grant it.
3284 case PHAuthorizationStatusRestricted:
3285 [self displayPrivacyErrorAlertOnMainQueue:
3286 l10n_util::GetNSString(
3287 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3288 break;
3289
3290 // The application doesn't have permission to access photo and the user
3291 // can grant it.
3292 case PHAuthorizationStatusDenied:
3293 [self displayImageErrorAlertWithSettingsOnMainQueue];
3294 break;
3295
3296 // The application has permission to access the photos.
3297 default: {
gambard9efce7a2017-02-09 18:53:173298 web::WebThread::PostTask(
stkhapuginf58b10d02017-04-10 13:36:173299 web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
gambard9efce7a2017-02-09 18:53:173300 [self saveImage:data withFileExtension:fileExtension];
3301 }));
sdefresnee65fd872016-12-19 13:38:133302 break;
3303 }
3304 }
3305}
3306
gambard9efce7a2017-02-09 18:53:173307- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension {
gambard16cfe1f2016-12-21 13:12:093308 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
gambard9efce7a2017-02-09 18:53:173309 stringByAppendingString:fileExtension];
sdefresnee65fd872016-12-19 13:38:133310 NSURL* fileURL =
3311 [NSURL fileURLWithPath:[NSTemporaryDirectory()
3312 stringByAppendingPathComponent:fileName]];
3313 NSError* error = nil;
3314 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3315
3316 // Error while writing the image to disk.
3317 if (error) {
3318 NSString* errorMessage = [NSString
3319 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3320 [error domain], [error code]];
3321 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3322 return;
3323 }
3324
3325 // Save the image to photos.
3326 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3327 [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
3328 }
3329 completionHandler:^(BOOL success, NSError* error) {
3330 // Callback for the image saving.
3331 [self finishSavingImageWithError:error];
3332
3333 // Cleanup the temporary file.
3334 web::WebThread::PostTask(
stkhapuginf58b10d02017-04-10 13:36:173335 web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
sdefresnee65fd872016-12-19 13:38:133336 NSError* error = nil;
3337 [[NSFileManager defaultManager] removeItemAtURL:fileURL
3338 error:&error];
3339 }));
3340 }];
3341}
3342
3343- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3344 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3345 BOOL canGoToSetting =
3346 [[UIApplication sharedApplication] canOpenURL:settingURL];
3347 if (canGoToSetting) {
3348 dispatch_async(dispatch_get_main_queue(), ^{
3349 [self displayImageErrorAlertWithSettings:settingURL];
3350 });
3351 } else {
3352 [self displayPrivacyErrorAlertOnMainQueue:
3353 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3354 }
3355}
3356
3357- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3358 // Dismiss current alert.
3359 [_alertCoordinator stop];
3360
3361 NSString* title =
3362 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3363 NSString* message = l10n_util::GetNSString(
3364 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3365
stkhapuginc9eee7b2017-04-10 15:49:273366 _alertCoordinator =
3367 [[AlertCoordinator alloc] initWithBaseViewController:self
3368 title:title
3369 message:message];
sdefresnee65fd872016-12-19 13:38:133370
3371 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3372 action:nil
3373 style:UIAlertActionStyleCancel];
3374
3375 [_alertCoordinator
3376 addItemWithTitle:l10n_util::GetNSString(
3377 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3378 action:^{
3379 OpenUrlWithCompletionHandler(settingURL, nil);
3380 }
3381 style:UIAlertActionStyleDefault];
3382
3383 [_alertCoordinator start];
3384}
3385
3386- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3387 dispatch_async(dispatch_get_main_queue(), ^{
3388 NSString* title =
3389 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3390 [self showErrorAlertWithStringTitle:title message:errorContent];
3391 });
3392}
3393
3394// This callback is triggered when the image is effectively saved onto the photo
3395// album, or if the save failed for some reason.
3396- (void)finishSavingImageWithError:(NSError*)error {
3397 // Was there an error?
3398 if (error) {
3399 // Saving photo failed even though user has granted access to Photos.
3400 // Display the error information from the NSError object for user.
3401 NSString* errorMessage = [NSString
3402 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3403 [error domain], [error code]];
3404 // This code may be execute outside of the main thread. Make sure to display
3405 // the error on the main thread.
3406 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3407 } else {
3408 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3409 // the photo application. The current behaviour is to create the photo there
3410 // but not providing any link to it is suboptimal. That's what Safari is
3411 // doing, and what the PM want, but it doesn't make it right.
3412 }
3413}
3414
sdefresnee65fd872016-12-19 13:38:133415#pragma mark - Showing popups
3416
3417- (void)showToolsMenuPopup {
3418 DCHECK(_browserState);
3419 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133420
peterlaurensb04bf692017-05-19 21:06:033421 // Record the time this menu was requested; to be stored in the configuration
3422 // object.
3423 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
3424
sdefresnee65fd872016-12-19 13:38:133425 // Dismiss the omnibox (if open).
3426 [_toolbarController cancelOmniboxEdit];
3427 // Dismiss the soft keyboard (if open).
3428 [[_model currentTab].webController dismissKeyboard];
3429 // Dismiss Find in Page focus.
3430 [self updateFindBar:NO shouldFocus:NO];
3431
stkhapuginc9eee7b2017-04-10 15:49:273432 ToolsMenuConfiguration* configuration =
3433 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
peterlaurensb04bf692017-05-19 21:06:033434 configuration.requestStartTime =
3435 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
sdefresnee65fd872016-12-19 13:38:133436 if ([_model count] == 0)
liaoyukeea9f3ee62017-03-07 22:05:393437 [configuration setNoOpenedTabs:YES];
3438
sdefresnee65fd872016-12-19 13:38:133439 if (_isOffTheRecord)
liaoyukeea9f3ee62017-03-07 22:05:393440 [configuration setInIncognito:YES];
3441
gambard65d69152017-03-23 17:44:223442 if (!_readingListMenuNotifier) {
stkhapuginc9eee7b2017-04-10 15:49:273443 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
gambard65d69152017-03-23 17:44:223444 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
stkhapuginc9eee7b2017-04-10 15:49:273445 _browserState)];
sdefresnee65fd872016-12-19 13:38:133446 }
gambard65d69152017-03-23 17:44:223447 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
sdefresnee65fd872016-12-19 13:38:133448
liaoyukeea9f3ee62017-03-07 22:05:393449 [configuration setUserAgentType:self.userAgentType];
3450
3451 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
3452
sdefresnee65fd872016-12-19 13:38:133453 ToolsPopupController* toolsPopupController =
3454 [_toolbarController toolsPopupController];
3455 if ([_model currentTab]) {
3456 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
3457 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
3458 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
3459 [toolsPopupController setCanUseReaderMode:self.canUseReaderMode];
sdefresnee65fd872016-12-19 13:38:133460 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
3461
3462 if (!IsIPadIdiom())
3463 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
3464 }
3465}
3466
3467- (void)showPageInfoPopupForView:(UIView*)sourceView {
3468 Tab* tab = [_model currentTab];
3469 DCHECK([tab navigationManager]);
3470 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3471
3472 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3473 // be trigerred by a button enabled when a current item matches some
3474 // conditions. However a crash was seen were navItem was NULL hence this
3475 // test after a DCHECK.
3476 DCHECK(navItem);
3477 if (!navItem)
3478 return;
3479
olivierrobin013ba672017-03-01 21:16:243480 // Don't show if the page is native except for offline pages (to show the
3481 // offline page info).
3482 if ([self isTabNativePage:tab] &&
3483 !reading_list::IsOfflineURL(navItem->GetURL())) {
sdefresnee65fd872016-12-19 13:38:133484 return;
olivierrobin013ba672017-03-01 21:16:243485 }
sdefresnee65fd872016-12-19 13:38:133486
justincohenb1a73cf2017-02-06 20:25:433487 // Don't show the bubble twice (this can happen when tapping very quickly in
3488 // accessibility mode).
3489 if (_pageInfoController)
3490 return;
3491
sdefresnee65fd872016-12-19 13:38:133492 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3493
3494 // Dismiss the omnibox (if open).
3495 [_toolbarController cancelOmniboxEdit];
3496
3497 [[NSNotificationCenter defaultCenter]
3498 postNotificationName:ios_internal::kPageInfoWillShowNotification
3499 object:nil];
3500
3501 // TODO(rohitrao): Get rid of PageInfoModel completely.
3502 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3503 PageInfoModel* pageInfoModel = new PageInfoModel(
3504 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3505
3506 UIView* view = [self view];
stkhapuginc9eee7b2017-04-10 15:49:273507 _pageInfoController = [[PageInfoViewController alloc]
sdefresnee65fd872016-12-19 13:38:133508 initWithModel:pageInfoModel
3509 bridge:bridge
3510 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
stkhapuginc9eee7b2017-04-10 15:49:273511 parentView:view];
3512 bridge->set_controller(_pageInfoController);
sdefresnee65fd872016-12-19 13:38:133513}
3514
3515- (void)hidePageInfoPopupForView:(UIView*)sourceView {
3516 [_pageInfoController dismiss];
stkhapuginc9eee7b2017-04-10 15:49:273517 _pageInfoController = nil;
sdefresnee65fd872016-12-19 13:38:133518}
3519
3520- (void)showSecurityHelpPage {
3521 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3522 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133523 inBackground:NO
3524 appendTo:kCurrentTab];
3525 [self hidePageInfoPopupForView:nil];
3526}
3527
3528- (void)showTabHistoryPopupForBackwardHistory {
3529 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133530
3531 // Dismiss the omnibox (if open).
3532 [_toolbarController cancelOmniboxEdit];
3533 // Dismiss the soft keyboard (if open).
3534 Tab* tab = [_model currentTab];
3535 [tab.webController dismissKeyboard];
3536
kkhorimoto1c12cbf2017-03-14 02:57:133537 web::NavigationItemList backwardItems =
3538 [tab navigationManager]->GetBackwardItems();
sdefresnee65fd872016-12-19 13:38:133539 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133540 withItems:backwardItems
sdefresnee65fd872016-12-19 13:38:133541 forBackHistory:YES];
3542}
3543
3544- (void)showTabHistoryPopupForForwardHistory {
3545 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133546
3547 // Dismiss the omnibox (if open).
3548 [_toolbarController cancelOmniboxEdit];
3549 // Dismiss the soft keyboard (if open).
3550 Tab* tab = [_model currentTab];
3551 [tab.webController dismissKeyboard];
3552
kkhorimoto1c12cbf2017-03-14 02:57:133553 web::NavigationItemList forwardItems =
3554 [tab navigationManager]->GetForwardItems();
sdefresnee65fd872016-12-19 13:38:133555 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133556 withItems:forwardItems
sdefresnee65fd872016-12-19 13:38:133557 forBackHistory:NO];
3558}
3559
3560- (void)navigateToSelectedEntry:(id)sender {
3561 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
3562 TabHistoryCell* selectedCell = (TabHistoryCell*)sender;
kkhorimoto7aed9e262017-03-04 02:28:553563 [[_model currentTab] goToItem:selectedCell.item];
sdefresnee65fd872016-12-19 13:38:133564 [_toolbarController dismissTabHistoryPopup];
3565}
3566
3567- (void)print {
3568 Tab* currentTab = [_model currentTab];
3569 // The UI should prevent users from printing non-printable pages. However, a
3570 // redirection to an un-printable page can happen before it is reflected in
3571 // the UI.
3572 if (![currentTab viewForPrinting]) {
pinkerton07e27842017-03-02 15:29:023573 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
sdefresnee65fd872016-12-19 13:38:133574 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
3575 return;
3576 }
3577 DCHECK(_browserState);
stkhapuginc9eee7b2017-04-10 15:49:273578 if (!_printController) {
3579 _printController = [[PrintController alloc]
3580 initWithContextGetter:_browserState->GetRequestContext()];
sdefresnee65fd872016-12-19 13:38:133581 }
3582 [_printController printView:[currentTab viewForPrinting]
3583 withTitle:[currentTab title]
3584 viewController:self];
3585}
3586
3587- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133588 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3589
3590 ReadingListModel* readingModel =
3591 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013592 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3593 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133594
pinkerton07e27842017-03-02 15:29:023595 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033596 [self showSnackbar:l10n_util::GetNSString(
3597 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133598}
3599
3600#pragma mark - Keyboard commands management
3601
3602- (BOOL)shouldRegisterKeyboardCommands {
3603 if ([self presentedViewController])
3604 return NO;
3605
3606 if (_voiceSearchController && _voiceSearchController->IsVisible())
3607 return NO;
3608
3609 // If there is no first responder, try to make the webview the first
3610 // responder.
3611 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273612 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133613 }
3614
3615 return YES;
3616}
3617
3618- (KeyCommandsProvider*)keyCommandsProvider {
3619 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273620 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133621 }
stkhapuginc9eee7b2017-04-10 15:49:273622 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133623}
3624
3625#pragma mark - KeyCommandsPlumbing
3626
3627- (BOOL)isOffTheRecord {
3628 return _isOffTheRecord;
3629}
3630
3631- (NSUInteger)tabsCount {
3632 return [_model count];
3633}
3634
lpromero47ea8862017-01-13 17:51:063635- (BOOL)canGoBack {
3636 return [_model currentTab].canGoBack;
3637}
3638
3639- (BOOL)canGoForward {
3640 return [_model currentTab].canGoForward;
3641}
3642
sdefresnee65fd872016-12-19 13:38:133643- (void)focusTabAtIndex:(NSUInteger)index {
3644 if ([_model count] > index) {
3645 [_model setCurrentTab:[_model tabAtIndex:index]];
3646 }
3647}
3648
3649- (void)focusNextTab {
3650 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3651 NSInteger modelCount = [_model count];
3652 if (currentTabIndex < modelCount - 1) {
3653 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3654 [_model setCurrentTab:nextTab];
3655 } else {
3656 [_model setCurrentTab:[_model tabAtIndex:0]];
3657 }
3658}
3659
3660- (void)focusPreviousTab {
3661 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3662 if (currentTabIndex > 0) {
3663 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3664 [_model setCurrentTab:previousTab];
3665 } else {
3666 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3667 [_model setCurrentTab:lastTab];
3668 }
3669}
3670
3671- (void)reopenClosedTab {
3672 sessions::TabRestoreService* const tabRestoreService =
3673 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3674 if (!tabRestoreService || tabRestoreService->entries().empty())
3675 return;
3676
3677 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3678 tabRestoreService->entries().front();
3679 // Only handle the TAB type.
3680 if (entry->type != sessions::TabRestoreService::TAB)
3681 return;
3682
3683 [self chromeExecuteCommand:[GenericChromeCommand commandWithTag:IDC_NEW_TAB]];
3684 TabRestoreServiceDelegateImplIOS* const delegate =
3685 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3686 _browserState);
3687 tabRestoreService->RestoreEntryById(delegate, entry->id,
3688 WindowOpenDisposition::CURRENT_TAB);
3689}
3690
3691- (void)focusOmnibox {
3692 [_toolbarController focusOmnibox];
3693}
3694
3695#pragma mark - UIResponder
3696
3697- (NSArray*)keyCommands {
3698 if (![self shouldRegisterKeyboardCommands]) {
3699 return nil;
3700 }
3701 return [self.keyCommandsProvider
3702 keyCommandsForConsumer:self
3703 editingText:![self isFirstResponder]];
3704}
3705
3706#pragma mark -
3707
3708// Induce an intentional crash in the browser process.
3709- (void)induceBrowserCrash {
3710 CHECK(false);
3711 // Call another function, so that the above CHECK can't be tail-call
3712 // optimized. This ensures that this method's name will show up in the stack
3713 // for easier identification.
3714 CHECK(true);
3715}
3716
3717- (void)loadURL:(const GURL&)url
3718 referrer:(const web::Referrer&)referrer
3719 transition:(ui::PageTransition)transition
3720 rendererInitiated:(BOOL)rendererInitiated {
3721 [[OmniboxGeolocationController sharedInstance]
3722 locationBarDidSubmitURL:url
3723 transition:transition
3724 browserState:_browserState];
3725
3726 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3727 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3728 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3729 }
3730
3731 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243732 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133733 // restart.
3734 if (url.host() == kChromeUIBrowserCrashHost) {
3735 [self induceBrowserCrash];
3736 // In debug the app can continue working even after the CHECK. Adding a
3737 // return avoids the crash url to be added to the history.
3738 return;
3739 }
3740
3741 if (url == [_preloadController prerenderedURL]) {
sdefresne2c600c52017-04-04 16:49:593742 std::unique_ptr<web::WebState> newWebState =
3743 [_preloadController releasePrerenderContents];
3744 DCHECK(newWebState);
3745
sdefresnee65fd872016-12-19 13:38:133746 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593747 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133748 DCHECK(oldTab);
3749 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593750
kkhorimotod804c5732017-03-15 23:44:523751 bool canPruneItems =
3752 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593753
kkhorimotod804c5732017-03-15 23:44:523754 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523755 [newTab navigationManager]->CopyStateFromAndPrune(
3756 [oldTab navigationManager]);
sdefresnee65fd872016-12-19 13:38:133757 [[newTab nativeAppNavigationController]
3758 copyStateFrom:[oldTab nativeAppNavigationController]];
sdefresne2c600c52017-04-04 16:49:593759
3760 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3761 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133762
3763 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3764 // BrowserViewController to detect that a pre-rendered tab is switched in,
3765 // and show the prerendering animation.
3766 newTab.isPrerenderTab = NO;
3767
sdefresne2f7781c2017-03-02 19:12:463768 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133769 return;
3770 }
3771 }
3772
3773 GURL urlToLoad = url;
3774 if ([_preloadController hasPrefetchedURL:url]) {
3775 // Prefetched URLs have modified URLs, so load the prefetched version of
3776 // |url| instead of the original |url|.
3777 urlToLoad = [_preloadController prefetchedURL];
3778 }
3779
3780 [_preloadController cancelPrerender];
3781
3782 // Some URLs are not allowed while in incognito. If we are in incognito and
3783 // load a disallowed URL, instead create a new tab not in the incognito state.
3784 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3785 [self webPageOrderedOpen:url
3786 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133787 inIncognito:NO
3788 inBackground:NO
3789 appendTo:kCurrentTab];
3790 return;
3791 }
3792
mrefaata84d5a02017-06-08 17:13:293793 // If this is a reload initiated from the omnibox.
3794 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3795 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3796 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3797 [[_model currentTab] navigationManager]->Reload(
3798 web::ReloadType::NORMAL, true /* check_for_repost */);
3799 return;
3800 }
3801
sdefresnee65fd872016-12-19 13:38:133802 web::NavigationManager::WebLoadParams params(urlToLoad);
3803 params.referrer = referrer;
3804 params.transition_type = transition;
3805 params.is_renderer_initiated = rendererInitiated;
3806 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233807 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133808}
3809
3810- (void)loadJavaScriptFromLocationBar:(NSString*)script {
3811 [_preloadController cancelPrerender];
3812 DCHECK([_model currentTab]);
3813 [[_model currentTab].webController executeUserJavaScript:script
3814 completionHandler:nil];
3815}
3816
3817- (web::WebState*)currentWebState {
3818 return [[_model currentTab] webState];
3819}
3820
3821// This is called from within an animation block.
3822- (void)toolbarHeightChanged {
3823 if ([self headerHeight] != 0) {
3824 // Ensure full screen height is updated.
3825 Tab* currentTab = [_model currentTab];
3826 BOOL visible = self.isToolbarOnScreen;
3827 [currentTab updateFullscreenWithToolbarVisible:visible];
3828 }
3829}
3830
3831// Load a new URL on a new page/tab.
3832- (void)webPageOrderedOpen:(const GURL&)URL
3833 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133834 inBackground:(BOOL)inBackground
3835 appendTo:(OpenPosition)appendTo {
3836 Tab* adjacentTab = nil;
3837 if (appendTo == kCurrentTab)
3838 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353839 [_model insertTabWithURL:URL
3840 referrer:referrer
3841 transition:ui::PAGE_TRANSITION_LINK
3842 opener:adjacentTab
3843 openedByDOM:NO
3844 atIndex:TabModelConstants::kTabPositionAutomatically
3845 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133846}
3847
3848- (void)webPageOrderedOpen:(const GURL&)url
3849 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133850 inIncognito:(BOOL)inIncognito
3851 inBackground:(BOOL)inBackground
3852 appendTo:(OpenPosition)appendTo {
3853 if (inIncognito == _isOffTheRecord) {
3854 [self webPageOrderedOpen:url
3855 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133856 inBackground:inBackground
3857 appendTo:appendTo];
3858 return;
3859 }
3860 // When sending an open command that switches modes, ensure the tab
3861 // ends up appended to the end of the model, not just next to what is
3862 // currently selected in the other mode. This is done with the |append|
3863 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273864 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133865 initWithURL:url
3866 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133867 inIncognito:inIncognito
3868 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273869 appendTo:kLastTab];
sdefresnee65fd872016-12-19 13:38:133870 [self chromeExecuteCommand:command];
3871}
3872
3873- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
3874 [[_model currentTab] loadSessionTab:sessionTab];
3875}
3876
3877- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433878 DCHECK(javascript);
3879 javascript = [javascript stringByRemovingPercentEncoding];
3880 web::WebState* webState = [[_model currentTab] webState];
3881 if (webState) {
3882 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3883 }
sdefresnee65fd872016-12-19 13:38:133884}
3885
3886#pragma mark - WebToolbarDelegate methods
3887
3888- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3889 if (_locationBarHasFocus)
3890 return; // TODO(crbug.com/244366): This should not be necessary.
3891 _locationBarHasFocus = YES;
3892 [[NSNotificationCenter defaultCenter]
3893 postNotificationName:ios_internal::
3894 kLocationBarBecomesFirstResponderNotification
3895 object:nil];
3896 [_sideSwipeController setEnabled:NO];
3897 if ([[_model currentTab].webController wantsKeyboardShield]) {
3898 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3899 [_typingShield setAlpha:0.0];
3900 [_typingShield setHidden:NO];
3901 [UIView animateWithDuration:0.3
3902 animations:^{
3903 [_typingShield setAlpha:1.0];
3904 }];
3905 }
3906 [[OmniboxGeolocationController sharedInstance]
3907 locationBarDidBecomeFirstResponder:_browserState];
3908}
3909
3910- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3911 if (!_locationBarHasFocus)
3912 return; // TODO(crbug.com/244366): This should not be necessary.
3913 _locationBarHasFocus = NO;
3914 [_sideSwipeController setEnabled:YES];
3915 [[NSNotificationCenter defaultCenter]
3916 postNotificationName:ios_internal::
3917 kLocationBarResignsFirstResponderNotification
3918 object:nil];
3919 [UIView animateWithDuration:0.3
3920 animations:^{
3921 [_typingShield setAlpha:0.0];
3922 }
3923 completion:^(BOOL finished) {
3924 // This can happen if one quickly resigns the omnibox and then taps
3925 // on the omnibox again during this animation. If the animation is
3926 // interrupted and the toolbar controller is first responder, it's safe
3927 // to assume the |_typingShield| shouldn't be hidden here.
3928 if (!finished && [_toolbarController isOmniboxFirstResponder])
3929 return;
3930 [_typingShield setHidden:YES];
3931 }];
3932 [[OmniboxGeolocationController sharedInstance]
3933 locationBarDidResignFirstResponder:_browserState];
3934
3935 // If a load was cancelled by an omnibox edit, but nothing is loading when
3936 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3937 if (_locationBarEditCancelledLoad) {
3938 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:293939
3940 web::WebState* webState = [_model currentTab].webState;
3941 if (!_toolbarModelIOS->IsLoading() && webState)
3942 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3943 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133944 }
3945}
3946
3947- (IBAction)locationBarBeganEdit:(id)sender {
3948 // On handsets, if a page is currently loading it should be stopped.
3949 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
stkhapuginc9eee7b2017-04-10 15:49:273950 GenericChromeCommand* command =
3951 [[GenericChromeCommand alloc] initWithTag:IDC_STOP];
sdefresnee65fd872016-12-19 13:38:133952 [self chromeExecuteCommand:command];
3953 _locationBarEditCancelledLoad = YES;
3954 }
3955}
3956
3957- (IBAction)prepareToEnterTabSwitcher:(id)sender {
3958 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
3959}
3960
3961- (ToolbarModelIOS*)toolbarModelIOS {
3962 return _toolbarModelIOS.get();
3963}
3964
3965- (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
3966 [_toolbarController setBackgroundAlpha:alpha];
3967}
3968
3969- (void)updateToolbarControlsAlpha:(CGFloat)alpha {
3970 [_toolbarController setControlsAlpha:alpha];
3971}
3972
3973- (void)willUpdateToolbarSnapshot {
3974 [[_model currentTab].overscrollActionsController clear];
3975}
3976
3977- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
3978 CGRect frame = [_contentArea frame];
3979 if (!fullScreen) {
3980 // Changing the origin here is unnecessary, it's set in page_animation_util.
3981 frame.size.height -= [self headerHeight];
3982 }
3983
3984 CGFloat shortAxis = frame.size.width;
3985 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
3986 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
3987 CGFloat aspectRatio = frame.size.height / frame.size.width;
3988 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
3989 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
3990 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
3991 CGRect cardFrame = {frame.origin, cardSize};
3992
3993 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:173994 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133995 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
3996 : CardCloseButtonSide::LEADING;
3997 [_contentArea addSubview:card];
3998 return card;
3999}
4000
4001#pragma mark - Command Handling
4002
4003- (IBAction)chromeExecuteCommand:(id)sender {
4004 NSInteger command = [sender tag];
4005
4006 if (!_model || !_browserState)
4007 return;
rohitrao005a6432017-03-16 20:52:424008 Tab* currentTab = [_model currentTab];
sdefresnee65fd872016-12-19 13:38:134009
4010 switch (command) {
4011 case IDC_BACK:
lpromero47ea8862017-01-13 17:51:064012 [[_model currentTab] goBack];
sdefresnee65fd872016-12-19 13:38:134013 break;
4014 case IDC_BOOKMARK_PAGE:
4015 [self initializeBookmarkInteractionController];
4016 [_bookmarkInteractionController
4017 presentBookmarkForTab:[_model currentTab]
4018 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
4019 inView:[_toolbarController bookmarkButtonView]
4020 originRect:[_toolbarController bookmarkButtonAnchorRect]];
4021 break;
4022 case IDC_CLOSE_TAB:
4023 [self closeCurrentTab];
4024 break;
4025 case IDC_FIND:
4026 [self initFindBarForTab];
4027 break;
rohitraob2bf3cb2017-02-10 14:10:364028 case IDC_FIND_NEXT: {
rohitrao005a6432017-03-16 20:52:424029 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134030 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424031 FindTabHelper::FromWebState(currentTab.webState)
4032 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4033 [_findBarController updateResultsCount:model];
4034 });
sdefresnee65fd872016-12-19 13:38:134035 break;
rohitraob2bf3cb2017-02-10 14:10:364036 }
4037 case IDC_FIND_PREVIOUS: {
rohitrao005a6432017-03-16 20:52:424038 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:134039 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:424040 FindTabHelper::FromWebState(currentTab.webState)
4041 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4042 [_findBarController updateResultsCount:model];
4043 });
sdefresnee65fd872016-12-19 13:38:134044 break;
rohitraob2bf3cb2017-02-10 14:10:364045 }
sdefresnee65fd872016-12-19 13:38:134046 case IDC_FIND_CLOSE:
4047 [self closeFindInPage];
4048 break;
4049 case IDC_FIND_UPDATE:
4050 [self searchFindInPage];
4051 break;
4052 case IDC_FORWARD:
lpromero47ea8862017-01-13 17:51:064053 [[_model currentTab] goForward];
sdefresnee65fd872016-12-19 13:38:134054 break;
4055 case IDC_FULLSCREEN:
4056 NOTIMPLEMENTED();
4057 break;
4058 case IDC_HELP_PAGE_VIA_MENU:
4059 [self showHelpPage];
4060 break;
4061 case IDC_NEW_TAB:
4062 if (_isOffTheRecord) {
4063 // Not for this browser state, send it on its way.
4064 [super chromeExecuteCommand:sender];
4065 } else {
4066 [self newTab:sender];
4067 }
4068 break;
4069 case IDC_PRELOAD_VOICE_SEARCH:
4070 // Preload VoiceSearchController and views and view controllers needed
4071 // for voice search.
4072 [self ensureVoiceSearchControllerCreated];
4073 _voiceSearchController->PrepareToAppear();
4074 break;
4075 case IDC_NEW_INCOGNITO_TAB:
4076 if (_isOffTheRecord) {
4077 [self newTab:sender];
4078 } else {
4079 // Not for this browser state, send it on its way.
4080 [super chromeExecuteCommand:sender];
4081 }
4082 break;
liaoyuke563dc4a2017-03-17 18:36:294083 case IDC_RELOAD: {
4084 web::WebState* webState = [_model currentTab].webState;
4085 if (webState)
4086 // |check_for_repost| is true because the reload is explicitly initiated
4087 // by the user.
4088 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4089 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134090 break;
liaoyuke563dc4a2017-03-17 18:36:294091 }
sdefresnee65fd872016-12-19 13:38:134092 case IDC_SHARE_PAGE:
4093 [self sharePage];
4094 break;
4095 case IDC_SHOW_MAIL_COMPOSER:
4096 [self showMailComposer:sender];
4097 break;
4098 case IDC_READER_MODE:
4099 [[_model currentTab] switchToReaderMode];
4100 break;
4101 case IDC_REQUEST_DESKTOP_SITE:
liaoyuke6ab362012017-04-12 16:10:074102 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
sdefresnee65fd872016-12-19 13:38:134103 break;
liaoyukeea9f3ee62017-03-07 22:05:394104 case IDC_REQUEST_MOBILE_SITE:
Yuke Liao705611d2017-06-01 18:03:064105 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
liaoyukeea9f3ee62017-03-07 22:05:394106 break;
sdefresnee65fd872016-12-19 13:38:134107 case IDC_SHOW_TOOLS_MENU: {
jif7fed8122017-02-08 13:15:254108 [self showToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134109 break;
4110 }
4111 case IDC_SHOW_BOOKMARK_MANAGER: {
4112 if (IsIPadIdiom()) {
4113 [self showAllBookmarks];
4114 } else {
4115 [self initializeBookmarkInteractionController];
4116 [_bookmarkInteractionController presentBookmarks];
4117 }
4118 break;
4119 }
4120 case IDC_SHOW_OTHER_DEVICES: {
4121 if (IsIPadIdiom()) {
4122 [self showNTPPanel:NewTabPage::kOpenTabsPanel];
4123 } else {
4124 UIViewController* controller = [RecentTabsPanelViewController
4125 controllerToPresentForBrowserState:_browserState
4126 loader:self];
4127 controller.modalPresentationStyle = UIModalPresentationFormSheet;
4128 controller.modalPresentationCapturesStatusBarAppearance = YES;
4129 [self presentViewController:controller animated:YES completion:nil];
4130 }
4131 break;
4132 }
4133 case IDC_STOP:
sdefresne7d699dd2017-04-05 13:05:234134 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:134135 break;
4136#if !defined(NDEBUG)
4137 case IDC_VIEW_SOURCE:
4138 [self viewSource];
4139 break;
4140#endif
4141 case IDC_SHOW_PAGE_INFO:
4142 DCHECK([sender isKindOfClass:[UIButton class]]);
4143 [self showPageInfoPopupForView:sender];
4144 break;
4145 case IDC_HIDE_PAGE_INFO:
4146 [[NSNotificationCenter defaultCenter]
4147 postNotificationName:ios_internal::kPageInfoWillHideNotification
4148 object:nil];
4149 [self hidePageInfoPopupForView:sender];
4150 break;
4151 case IDC_SHOW_SECURITY_HELP:
4152 [self showSecurityHelpPage];
4153 break;
4154 case IDC_SHOW_BACK_HISTORY:
4155 [self showTabHistoryPopupForBackwardHistory];
4156 break;
4157 case IDC_SHOW_FORWARD_HISTORY:
4158 [self showTabHistoryPopupForForwardHistory];
4159 break;
4160 case IDC_BACK_FORWARD_IN_TAB_HISTORY:
4161 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
4162 [self navigateToSelectedEntry:sender];
4163 break;
4164 case IDC_PRINT:
4165 [self print];
4166 break;
4167 case IDC_ADD_READING_LIST: {
sdefresnee65fd872016-12-19 13:38:134168 ReadingListAddCommand* command =
4169 base::mac::ObjCCastStrict<ReadingListAddCommand>(sender);
4170 [self addToReadingListURL:[command URL] title:[command title]];
4171 break;
4172 }
4173 case IDC_RATE_THIS_APP:
4174 [self showRateThisAppDialog];
4175 break;
4176 case IDC_SHOW_READING_LIST:
sdefresnee65fd872016-12-19 13:38:134177 [self showReadingList];
4178 break;
4179 case IDC_VOICE_SEARCH:
4180 // If the voice search command is coming from a UIView sender, store it
4181 // before sending the command up the responder chain.
4182 if ([sender isKindOfClass:[UIView class]])
stkhapuginc9eee7b2017-04-10 15:49:274183 _voiceSearchButton = sender;
sdefresnee65fd872016-12-19 13:38:134184 [super chromeExecuteCommand:sender];
4185 break;
4186 case IDC_SHOW_QR_SCANNER:
jif5f067c62017-02-03 17:36:434187 [self showQRScanner];
sdefresnee65fd872016-12-19 13:38:134188 break;
gambardd2e44fb2017-01-25 09:14:214189 case IDC_SHOW_SUGGESTIONS:
4190 if (experimental_flags::IsSuggestionsUIEnabled()) {
4191 [self showSuggestionsUI];
4192 }
4193 break;
sdefresnee65fd872016-12-19 13:38:134194 default:
4195 // Unknown commands get sent up the responder chain.
4196 [super chromeExecuteCommand:sender];
4197 break;
4198 }
4199}
4200
4201- (void)closeCurrentTab {
4202 Tab* currentTab = [_model currentTab];
4203 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4204 if (tabIndex == NSNotFound)
4205 return;
4206
jif7fed8122017-02-08 13:15:254207 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4208 // iPad.
sdefresnee65fd872016-12-19 13:38:134209 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4210 exitingPage.image =
4211 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4212
4213 // Close the actual tab, and add its image as a subview.
4214 [_model closeTabAtIndex:tabIndex];
4215
4216 // Do not animate close in iPad.
4217 if (!IsIPadIdiom()) {
4218 [_contentArea addSubview:exitingPage];
4219 ios_internal::page_animation_util::AnimateOutWithCompletion(
4220 exitingPage, 0, YES, IsPortrait(), ^{
4221 [exitingPage removeFromSuperview];
4222 });
4223 }
4224}
4225
sdefresnee65fd872016-12-19 13:38:134226- (void)sharePage {
jif4a8cf942017-02-03 12:05:244227 ShareToData* data = activity_services::ShareToDataForTab([_model currentTab]);
sdefresnee65fd872016-12-19 13:38:134228 if (data)
4229 [self sharePageWithData:data];
4230}
4231
4232- (void)sharePageWithData:(ShareToData*)data {
4233 id<ShareProtocol> controller = [_dependencyFactory shareControllerInstance];
4234 if ([controller isActive])
4235 return;
4236 CGRect fromRect = [_toolbarController shareButtonAnchorRect];
4237 UIView* inView = [_toolbarController shareButtonView];
4238 [controller shareWithData:data
4239 controller:self
4240 browserState:_browserState
4241 shareToDelegate:self
4242 fromRect:fromRect
4243 inView:inView];
4244}
4245
4246- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
4247 [[_dependencyFactory shareControllerInstance] cancelShareAnimated:NO];
4248 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4249 [_bookmarkInteractionController dismissSnackbar];
4250 [_toolbarController cancelOmniboxEdit];
4251 [_dialogPresenter cancelAllDialogs];
4252 [self hidePageInfoPopupForView:nil];
4253 if (_voiceSearchController)
4254 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364255
4256 Tab* currentTab = [_model currentTab];
4257 [currentTab dismissModals];
4258
rohitrao005a6432017-03-16 20:52:424259 if (currentTab) {
4260 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4261 if (findHelper) {
4262 findHelper->StopFinding(^{
4263 [self updateFindBar:NO shouldFocus:NO];
4264 });
4265 }
4266 }
rohitraob2bf3cb2017-02-10 14:10:364267
sdefresnee65fd872016-12-19 13:38:134268 [_contextualSearchController movePanelOffscreen];
sdefresnee65fd872016-12-19 13:38:134269 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134270 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274271 _printController = nil;
jif7fed8122017-02-08 13:15:254272 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134273 [_contextMenuCoordinator stop];
4274 [self dismissRateThisAppDialog];
4275
gambardd2e44fb2017-01-25 09:14:214276 [_contentSuggestionsCoordinator stop];
4277
sdefresnee65fd872016-12-19 13:38:134278 if (self.presentedViewController) {
4279 // Dismisses any other modal controllers that may be present, e.g. Recent
4280 // Tabs.
4281 // Note that currently, some controllers like the bookmark ones were already
4282 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4283 // but are still reported as the presentedViewController. The result is that
4284 // this will call -dismissViewControllerAnimated:completion: a second time
4285 // on it. It is not per se an issue, as it is a no-op. The problem is that
4286 // in such a case, the completion block is not called.
4287 // To ensure the completion is called, nil is passed here, and the
4288 // completion is called below.
4289 [self dismissViewControllerAnimated:NO completion:nil];
4290 // Dismissed controllers will be so after a delay. Queue the completion
4291 // callback after that.
4292 if (completion) {
4293 dispatch_after(
4294 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4295 dispatch_get_main_queue(), ^{
4296 completion();
4297 });
4298 }
4299 } else if (completion) {
4300 // If no view controllers are presented, we should be ok with dispatching
4301 // the completion block directly.
4302 dispatch_async(dispatch_get_main_queue(), completion);
4303 }
4304}
4305
4306- (void)showHelpPage {
4307 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4308 [self webPageOrderedOpen:helpUrl
4309 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134310 inBackground:NO
4311 appendTo:kCurrentTab];
4312}
4313
sdefresnee65fd872016-12-19 13:38:134314- (void)resetAllWebViews {
eugenebutf8a138e62017-01-24 22:41:344315 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:134316 [_model resetAllWebViews];
4317}
4318
4319#pragma mark - Find Bar
4320
4321- (void)hideFindBarWithAnimation:(BOOL)animate {
4322 [_findBarController hideFindBarView:animate];
4323}
4324
4325- (void)showFindBarWithAnimation:(BOOL)animate
4326 selectText:(BOOL)selectText
4327 shouldFocus:(BOOL)shouldFocus {
4328 DCHECK(_findBarController);
4329 Tab* tab = [_model currentTab];
4330 DCHECK(tab);
4331 CRWWebController* webController = tab.webController;
4332
4333 CGRect referenceFrame = CGRectZero;
4334 if (IsIPadIdiom()) {
4335 referenceFrame = webController.visibleFrame;
4336 referenceFrame.origin.y -= kIPadFindBarOverlap;
4337 } else {
4338 referenceFrame = _contentArea.frame;
4339 }
4340
4341 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4342 [_findBarController addFindBarView:animate
4343 intoView:self.view
4344 withFrame:referenceFrame
4345 alignWithFrame:omniboxFrame
4346 selectText:selectText];
4347 [self updateFindBar:YES shouldFocus:shouldFocus];
4348}
4349
4350// Create find bar controller and pass it to the web controller.
4351- (void)initFindBarForTab {
4352 if (!self.canShowFindBar)
4353 return;
4354
4355 if (!_findBarController)
stkhapuginc9eee7b2017-04-10 15:49:274356 _findBarController =
4357 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134358
4359 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:424360 DCHECK(tab);
4361 auto* helper = FindTabHelper::FromWebState(tab.webState);
4362 DCHECK(!helper->IsFindUIActive());
4363 helper->SetFindUIActive(true);
sdefresnee65fd872016-12-19 13:38:134364 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4365}
4366
4367- (void)searchFindInPage {
rohitrao005a6432017-03-16 20:52:424368 DCHECK([_model currentTab]);
4369 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
stkhapuginc9eee7b2017-04-10 15:49:274370 __weak BrowserViewController* weakSelf = self;
4371 helper->StartFinding(
4372 [_findBarController searchTerm], ^(FindInPageModel* model) {
4373 BrowserViewController* strongSelf = weakSelf;
4374 if (!strongSelf) {
4375 return;
4376 }
4377 [strongSelf->_findBarController updateResultsCount:model];
4378 });
rohitrao005a6432017-03-16 20:52:424379
sdefresnee65fd872016-12-19 13:38:134380 if (!_isOffTheRecord)
rohitrao005a6432017-03-16 20:52:424381 helper->PersistSearchTerm();
sdefresnee65fd872016-12-19 13:38:134382}
4383
4384- (void)closeFindInPage {
stkhapuginc9eee7b2017-04-10 15:49:274385 __weak BrowserViewController* weakSelf = self;
rohitrao005a6432017-03-16 20:52:424386 Tab* currentTab = [_model currentTab];
4387 if (currentTab) {
4388 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4389 [weakSelf updateFindBar:NO shouldFocus:NO];
4390 });
4391 }
sdefresnee65fd872016-12-19 13:38:134392}
4393
4394- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
rohitrao005a6432017-03-16 20:52:424395 DCHECK([_model currentTab]);
4396 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4397 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134398 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424399 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134400 }
4401
4402 [self setFramesForHeaders:[self headerViews]
4403 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424404 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134405 initialUpdate:initialUpdate
4406 focusTextfield:shouldFocus];
4407 } else {
4408 [self hideFindBarWithAnimation:YES];
4409 }
4410}
4411
4412- (void)showAllBookmarks {
4413 DCHECK(self.visible || self.dismissingModal);
4414 GURL URL(kChromeUIBookmarksURL);
4415 Tab* tab = [_model currentTab];
4416 web::NavigationManager::WebLoadParams params(URL);
4417 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234418 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134419}
4420
4421- (void)showReadingList {
stkhapuginc9eee7b2017-04-10 15:49:274422 _readingListCoordinator = [[ReadingListCoordinator alloc]
gambard6299cc1d2017-02-21 13:06:034423 initWithBaseViewController:self
4424 browserState:self.browserState
stkhapuginc9eee7b2017-04-10 15:49:274425 loader:self];
gambard6299cc1d2017-02-21 13:06:034426
4427 [_readingListCoordinator start];
sdefresnee65fd872016-12-19 13:38:134428}
4429
4430- (void)showQRScanner {
stkhapuginc9eee7b2017-04-10 15:49:274431 _qrScannerViewController =
4432 [[QRScannerViewController alloc] initWithDelegate:_toolbarController];
sdefresnee65fd872016-12-19 13:38:134433 [self presentViewController:[_qrScannerViewController
4434 getViewControllerToPresent]
4435 animated:YES
4436 completion:nil];
4437}
4438
gambardd2e44fb2017-01-25 09:14:214439- (void)showSuggestionsUI {
4440 if (!_contentSuggestionsCoordinator) {
stkhapuginc9eee7b2017-04-10 15:49:274441 _contentSuggestionsCoordinator =
4442 [[ContentSuggestionsCoordinator alloc] initWithBaseViewController:self];
gambard9b6abde2017-02-20 12:13:554443 [_contentSuggestionsCoordinator setURLLoader:self];
gambardd2e44fb2017-01-25 09:14:214444 }
gambard0ac7f3e2017-02-01 14:53:144445 [_contentSuggestionsCoordinator setBrowserState:_browserState];
gambardd2e44fb2017-01-25 09:14:214446 [_contentSuggestionsCoordinator start];
4447}
4448
sdefresnee65fd872016-12-19 13:38:134449- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel {
4450 DCHECK(self.visible || self.dismissingModal);
4451 GURL url(kChromeUINewTabURL);
4452 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4453 if (fragment != "") {
4454 GURL::Replacements replacement;
4455 replacement.SetRefStr(fragment);
4456 url = url.ReplaceComponents(replacement);
4457 }
4458 Tab* tab = [_model currentTab];
4459 web::NavigationManager::WebLoadParams params(url);
4460 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234461 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134462}
4463
4464- (void)showRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274465 DCHECK(!_rateThisAppDialog);
sdefresnee65fd872016-12-19 13:38:134466
4467 // Store the current timestamp whenever this dialog is shown.
4468 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4469 base::Time::Now().ToInternalValue());
4470
4471 // Some versions of iOS7 do not support linking directly to the "Ratings and
4472 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4473 // links to the main appstore page for the Chrome app.
4474 NSURL* storeURL =
4475 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4476 @"MZStore.woa/wa/"
4477 @"viewContentsUserReviews?type=Purple+Software&id="
4478 @"535886823&pt=9008&ct=rating")];
4479
4480 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4481 [self clearPresentedStateWithCompletion:nil];
4482
stkhapuginc9eee7b2017-04-10 15:49:274483 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
sdefresnee65fd872016-12-19 13:38:134484 [_rateThisAppDialog setAppStoreURL:storeURL];
4485 [_rateThisAppDialog setDelegate:self];
4486 [_rateThisAppDialog show];
4487}
4488
4489- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274490 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134491 base::RecordAction(base::UserMetricsAction(
4492 "IOSRateThisAppDialogDismissedProgramatically"));
4493 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274494 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134495 }
4496}
4497
4498#if !defined(NDEBUG)
4499- (void)viewSource {
4500 Tab* tab = [_model currentTab];
4501 DCHECK(tab);
4502 CRWWebController* webController = tab.webController;
4503 NSString* script = @"document.documentElement.outerHTML;";
stkhapuginc9eee7b2017-04-10 15:49:274504 __weak Tab* weakTab = tab;
4505 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:134506 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
stkhapuginc9eee7b2017-04-10 15:49:274507 Tab* strongTab = weakTab;
sdefresnee65fd872016-12-19 13:38:134508 if (!strongTab)
4509 return;
4510 if (![result isKindOfClass:[NSString class]])
4511 result = @"Not an HTML page";
4512 std::string base64HTML;
4513 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4514 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
kkhorimotob110b262017-06-01 18:38:254515 web::Referrer referrer([strongTab lastCommittedURL],
4516 web::ReferrerPolicyDefault);
eugenebut91cfb3a2017-02-21 16:40:314517
4518 [[weakSelf tabModel]
sdefresnea6395912017-03-01 01:14:354519 insertTabWithURL:URL
4520 referrer:referrer
4521 transition:ui::PAGE_TRANSITION_LINK
4522 opener:strongTab
4523 openedByDOM:YES
4524 atIndex:TabModelConstants::kTabPositionAutomatically
4525 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:134526 };
4527 [webController executeJavaScript:script
4528 completionHandler:completionHandlerBlock];
4529}
4530#endif // !defined(NDEBUG)
4531
4532- (void)startVoiceSearch {
4533 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124534 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134535 _startVoiceSearchAfterNewTabAnimation = YES;
4536 return;
4537 }
4538
4539 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4540 // dismiss the keyboard.
4541 [self closeFindInPage];
4542 [[_model currentTab].webController dismissKeyboard];
4543
4544 // Ensure that voice search objects are created.
4545 [self ensureVoiceSearchControllerCreated];
4546 [self ensureVoiceSearchBarCreated];
4547
4548 // Present voice search.
4549 [_voiceSearchBar prepareToPresentVoiceSearch];
4550 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4551 [_toolbarController cancelOmniboxEdit];
4552}
4553
4554#pragma mark - ToolbarOwner
4555
4556- (ToolbarController*)relinquishedToolbarController {
4557 if (_isToolbarControllerRelinquished)
4558 return nil;
4559
4560 ToolbarController* relinquishedToolbarController = nil;
4561 if ([_toolbarController view].hidden) {
4562 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:254563 if (currentTab && UrlHasChromeScheme(currentTab.lastCommittedURL)) {
sdefresnee65fd872016-12-19 13:38:134564 // Use the native content controller's toolbar when the BVC's is hidden.
4565 id nativeController = [self nativeControllerForTab:currentTab];
4566 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4567 relinquishedToolbarController =
4568 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274569 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134570 }
4571 }
4572 } else {
stkhapuginc9eee7b2017-04-10 15:49:274573 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134574 }
4575 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4576 return relinquishedToolbarController;
4577}
4578
4579- (void)reparentToolbarController {
4580 if (_isToolbarControllerRelinquished) {
4581 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4582 // A native content controller's toolbar has been relinquished.
4583 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274584 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134585 } else if ([_findBarController isFindInPageShown]) {
4586 [self.view insertSubview:[_toolbarController view]
4587 belowSubview:[_findBarController view]];
4588 } else {
4589 [self.view addSubview:[_toolbarController view]];
4590 }
4591 if (_contextualSearchPanel) {
4592 // Move panel back into its correct place.
4593 [self.view insertSubview:_contextualSearchPanel
4594 aboveSubview:[_toolbarController view]];
4595 }
4596 _isToolbarControllerRelinquished = NO;
4597 }
4598}
4599
4600#pragma mark - TabModelObserver methods
4601
4602// Observer method, tab inserted.
4603- (void)tabModel:(TabModel*)model
4604 didInsertTab:(Tab*)tab
4605 atIndex:(NSUInteger)modelIndex
4606 inForeground:(BOOL)fg {
4607 DCHECK(tab);
4608 [self installDelegatesForTab:tab];
4609
4610 if (fg) {
4611 [_contextualSearchController setTab:tab];
4612 [_paymentRequestManager setWebState:tab.webState];
4613 }
4614}
4615
4616// Observer method, active tab changed.
4617- (void)tabModel:(TabModel*)model
4618 didChangeActiveTab:(Tab*)newTab
4619 previousTab:(Tab*)previousTab
4620 atIndex:(NSUInteger)index {
4621 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4622 // Currently this observer method is always called with a non-nil |newTab|,
4623 // but that may change in the future. Remove this DCHECK when it does.
4624 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274625 if (_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:134626 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4627 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4628 }
4629 [self updateVoiceSearchBarVisibilityAnimated:NO];
4630
4631 [_contextualSearchController setTab:newTab];
4632 [_paymentRequestManager setWebState:newTab.webState];
4633
4634 [self tabSelected:newTab];
4635 DCHECK_EQ(newTab, [model currentTab]);
4636 [self installDelegatesForTab:newTab];
4637}
4638
4639// Observer method, tab changed.
4640- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4641 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4642 if (tab == [_model currentTab]) {
4643 [self updateToolbar];
4644 // Disable contextual search when |tab| is a voice search result tab.
4645 BOOL enableContextualSearch = self.active && !tab.isVoiceSearchResultsTab;
4646 [_contextualSearchController enableContextualSearch:enableContextualSearch];
4647 }
4648}
4649
sdefresne49cf2862017-03-15 13:46:144650// Observer method, tab replaced.
4651- (void)tabModel:(TabModel*)model
4652 didReplaceTab:(Tab*)oldTab
4653 withTab:(Tab*)newTab
4654 atIndex:(NSUInteger)index {
4655 [self uninstallDelegatesForTab:oldTab];
4656 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264657
michaeldo79909fb2017-05-09 23:42:504658 if (_infoBarContainer) {
4659 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4660 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4661 }
4662
kkhorimotofa0844cc2017-03-20 17:01:264663 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4664 if (self.active && model.currentTab == newTab)
4665 [self displayTab:newTab isNewSelection:NO];
sdefresne49cf2862017-03-15 13:46:144666}
4667
sdefresnee65fd872016-12-19 13:38:134668// A tab has been removed, remove its views from display if necessary.
4669- (void)tabModel:(TabModel*)model
4670 didRemoveTab:(Tab*)tab
4671 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144672 [self uninstallDelegatesForTab:tab];
4673
sdefresnee65fd872016-12-19 13:38:134674 // Remove stored native controllers for the tab.
4675 [_nativeControllersForTabIDs removeObjectForKey:tab.tabId];
4676
4677 // Ignore changes while the tab stack view is visible (or while suspended).
4678 // The display will be refreshed when this view becomes active again.
4679 if (!self.visible || !model.webUsageEnabled)
4680 return;
4681
4682 // Remove the find bar for now.
4683 [self hideFindBarWithAnimation:NO];
4684}
4685
4686- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4687 if (tab == [model currentTab]) {
4688 [_contentArea displayContentView:nil];
4689 [_toolbarController selectedTabChanged];
4690 }
4691
4692 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4693 if ([model count] == 1) { // About to remove the last tab.
4694 [_contextualSearchController setTab:nil];
4695 [_paymentRequestManager setWebState:nil];
4696 }
4697}
4698
4699// Called when the number of tabs changes. Update the toolbar accordingly.
4700- (void)tabModelDidChangeTabCount:(TabModel*)model {
4701 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134702 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134703}
4704
4705#pragma mark - Upgrade Detection
4706
4707- (void)showUpgrade:(UpgradeCenter*)center {
4708 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274709 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134710 NSString* tabId = tab.tabId;
4711 DCHECK([tab infoBarManager]);
4712 [center addInfoBarToManager:[tab infoBarManager] forTabId:tabId];
4713 }
4714}
4715
4716#pragma mark - ContextualSearchControllerDelegate
4717
4718- (void)createTabFromContextualSearchController:(const GURL&)url {
4719 Tab* currentTab = [_model currentTab];
4720 DCHECK(currentTab);
4721 NSUInteger index = [_model indexOfTab:currentTab];
4722 [self addSelectedTabWithURL:url
4723 atIndex:index + 1
4724 transition:ui::PAGE_TRANSITION_LINK];
4725}
4726
4727- (void)promotePanelToTabProvidedBy:(id<ContextualSearchTabProvider>)tabProvider
4728 focusInput:(BOOL)focusInput {
4729 // Tell the panel it will be promoted.
4730 ContextualSearchPanelView* promotingPanel = _contextualSearchPanel;
4731 [promotingPanel prepareForPromotion];
4732
4733 // Make a new panel and tell the controller about it.
4734 _contextualSearchPanel = [self createPanelView];
4735 [self.view insertSubview:_contextualSearchPanel belowSubview:promotingPanel];
4736 [_contextualSearchController setPanel:_contextualSearchPanel];
4737
4738 // Figure out vertical offset.
4739 CGFloat offset = StatusBarHeight();
4740 if (IsIPadIdiom()) {
4741 offset = MAX(offset, CGRectGetMaxY([_tabStripController view].frame));
4742 }
4743
4744 // Transition steps: Animate the panel position, fade in the toolbar and
4745 // tab strip.
4746 ProceduralBlock transition = ^{
4747 [promotingPanel promoteToMatchSuperviewWithVerticalOffset:offset];
4748 [self updateToolbarControlsAlpha:1.0];
4749 [self updateToolbarBackgroundAlpha:1.0];
4750 [_tabStripController view].alpha = 1.0;
4751 };
4752
4753 // After the transition animation completes, add the tab to the tab model
4754 // (on iPad this triggers the tab strip animation too), then fade out the
4755 // transitioning panel and remove it.
4756 void (^completion)(BOOL) = ^(BOOL finished) {
4757 _contextualSearchMask.alpha = 0;
sdefresne2c600c52017-04-04 16:49:594758 std::unique_ptr<web::WebState> webState = [tabProvider releaseWebState];
4759 DCHECK(webState);
4760 DCHECK(webState->GetNavigationManager());
4761
4762 Tab* newTab = LegacyTabHelper::GetTabForWebState(webState.get());
4763 WebStateList* webStateList = [_model webStateList];
4764
4765 // Insert the new tab after the current tab.
4766 DCHECK_NE(webStateList->active_index(), WebStateList::kInvalidIndex);
4767 DCHECK_NE(webStateList->active_index(), INT_MAX);
4768 int insertion_index = webStateList->active_index() + 1;
4769 webStateList->InsertWebState(insertion_index, std::move(webState));
4770 webStateList->SetOpenerOfWebStateAt(insertion_index,
4771 [tabProvider webStateOpener]);
sdefresnee65fd872016-12-19 13:38:134772
4773 // Set isPrerenderTab to NO after inserting the tab. This will allow the
4774 // BrowserViewController to detect that a pre-rendered tab is switched in,
4775 // and show the prerendering animation. This needs to happen before the
4776 // tab is made the current tab.
4777 // This also enables contextual search (if otherwise applicable) on
4778 // |newTab|.
sdefresne2c600c52017-04-04 16:49:594779
sdefresnee65fd872016-12-19 13:38:134780 newTab.isPrerenderTab = NO;
4781 [_model setCurrentTab:newTab];
4782
sdefresne2f7781c2017-03-02 19:12:464783 if (newTab.loadFinished)
sdefresnee65fd872016-12-19 13:38:134784 [self tabLoadComplete:newTab withSuccess:YES];
4785
4786 if (focusInput) {
4787 [_toolbarController focusOmnibox];
4788 }
4789 _infoBarContainer->RestoreInfobars();
4790
4791 [UIView animateWithDuration:ios::material::kDuration2
4792 animations:^{
4793 promotingPanel.alpha = 0;
4794 }
4795 completion:^(BOOL finished) {
4796 [promotingPanel removeFromSuperview];
4797 }];
4798 };
4799
4800 [UIView animateWithDuration:ios::material::kDuration3
4801 animations:transition
4802 completion:completion];
4803}
4804
4805- (ContextualSearchPanelView*)createPanelView {
4806 PanelConfiguration* config;
4807 CGSize panelContainerSize = self.view.bounds.size;
4808 if (IsIPadIdiom()) {
4809 config = [PadPanelConfiguration
4810 configurationForContainerSize:panelContainerSize
4811 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4812 } else {
4813 config = [PhonePanelConfiguration
4814 configurationForContainerSize:panelContainerSize
4815 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4816 }
stkhapuginf58b10d02017-04-10 13:36:174817 ContextualSearchPanelView* newPanel =
4818 [[ContextualSearchPanelView alloc] initWithConfiguration:config];
sdefresnee65fd872016-12-19 13:38:134819 [newPanel addMotionObserver:self];
4820 [newPanel addMotionObserver:_contextualSearchMask];
4821 return newPanel;
4822}
4823
4824#pragma mark - ContextualSearchPanelMotionObserver
4825
4826- (void)panel:(ContextualSearchPanelView*)panel
4827 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
4828 // If the header is offset, it's offscreen (or moving offscreen) and the
4829 // toolbar shouldn't be opacity-adjusted by the contextual search panel.
4830 if ([self currentHeaderOffset] != 0)
4831 return;
4832
4833 CGFloat toolbarAlpha;
4834
4835 if (motion.state == ContextualSearch::PREVIEWING) {
4836 // As the panel moves past the previewing position, the toolbar should
4837 // become more transparent.
4838 toolbarAlpha = 1 - motion.gradation;
4839 } else if (motion.state == ContextualSearch::COVERING) {
4840 // The toolbar should be totally transparent when the panel is covering.
4841 toolbarAlpha = 0.0;
4842 } else {
4843 return;
4844 }
4845
4846 // On iPad, the toolbar doesn't go fully transparent, so map |toolbarAlpha|'s
4847 // [0-1.0] range to [0.5-1.0].
4848 if (IsIPadIdiom()) {
4849 toolbarAlpha = 0.5 + (toolbarAlpha * 0.5);
4850 [_tabStripController view].alpha = toolbarAlpha;
4851 }
4852
4853 [self updateToolbarControlsAlpha:toolbarAlpha];
4854 [self updateToolbarBackgroundAlpha:toolbarAlpha];
4855}
4856
4857- (void)panel:(ContextualSearchPanelView*)panel
4858 didChangeToState:(ContextualSearch::PanelState)toState
4859 fromState:(ContextualSearch::PanelState)fromState {
4860 if (toState == ContextualSearch::DISMISSED) {
4861 // Panel has become hidden.
4862 _infoBarContainer->RestoreInfobars();
4863 [self updateToolbarControlsAlpha:1.0];
4864 [self updateToolbarBackgroundAlpha:1.0];
4865 [_tabStripController view].alpha = 1.0;
4866 } else if (fromState == ContextualSearch::DISMISSED) {
4867 // Panel has become visible.
4868 _infoBarContainer->SuspendInfobars();
4869 }
4870}
4871
4872- (void)panelWillPromote:(ContextualSearchPanelView*)panel {
4873 [panel removeMotionObserver:self];
4874}
4875
4876- (CGFloat)currentHeaderHeight {
4877 return [self headerHeight] - [self currentHeaderOffset];
4878}
4879
4880#pragma mark - InfoBarControllerDelegate
4881
4882- (void)infoBarContainerStateChanged:(bool)isAnimating {
4883 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4884 DCHECK(infoBarContainerView);
4885 CGRect containerFrame = infoBarContainerView.frame;
4886 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4887 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4888 containerFrame.size.height = height;
4889 BOOL isViewVisible = self.visible;
4890 [UIView animateWithDuration:0.1
4891 animations:^{
4892 [infoBarContainerView setFrame:containerFrame];
4893 }
4894 completion:^(BOOL finished) {
4895 if (!isViewVisible)
4896 return;
4897 UIAccessibilityPostNotification(
4898 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4899 }];
4900}
4901
4902- (BOOL)shouldAutorotate {
4903 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4904 // Don't rotate if a voice search is being presented or dismissed. Once the
4905 // transition animations finish, only the Voice Search UIViewController's
4906 // |-shouldAutorotate| will be called.
4907 return NO;
4908 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4909 // Don't auto rotate if side swipe controller view says not to.
4910 return NO;
4911 } else {
4912 return [super shouldAutorotate];
4913 }
4914}
4915
4916// Always return yes, as this tap should work with various recognizers,
4917// including UITextTapRecognizer, UILongPressGestureRecognizer,
4918// UIScrollViewPanGestureRecognizer and others.
4919- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4920 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4921 (UIGestureRecognizer*)otherGestureRecognizer {
4922 return YES;
4923}
4924
4925// Tap gestures should only be recognized within |_contentArea|.
4926- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4927 CGPoint location = [gesture locationInView:self.view];
4928
4929 // Only allow touches on descendant views of |_contentArea|.
4930 UIView* hitView = [self.view hitTest:location withEvent:nil];
4931 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4932}
4933
4934#pragma mark - SideSwipeController Delegate Methods
4935
4936- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4937 DCHECK(!IsIPadIdiom());
4938 // Update frame incase orientation changed while |_contentArea| was out of
4939 // the view hierarchy.
4940 [_contentArea setFrame:[sideSwipeView frame]];
4941
4942 [self.view insertSubview:_contentArea atIndex:0];
4943 [self updateVoiceSearchBarVisibilityAnimated:NO];
4944 [self updateToolbar];
4945
4946 // Reset horizontal stack view.
4947 [sideSwipeView removeFromSuperview];
4948 [_sideSwipeController setInSwipe:NO];
4949 [_infoBarContainer->view() setHidden:NO];
4950}
4951
4952- (UIView*)contentView {
4953 return _contentArea;
4954}
4955
4956- (TabStripController*)tabStripController {
4957 return _tabStripController;
4958}
4959
4960- (WebToolbarController*)toolbarController {
4961 return _toolbarController;
4962}
4963
4964- (BOOL)preventSideSwipe {
4965 if ([_toolbarController toolsPopupController])
4966 return YES;
4967
4968 if (_voiceSearchController && _voiceSearchController->IsVisible())
4969 return YES;
4970
4971 if ([_contextualSearchPanel state] >= ContextualSearch::PEEKING)
4972 return YES;
4973
4974 if (!self.active)
4975 return YES;
4976
4977 return NO;
4978}
4979
4980- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4981 if (visible) {
4982 [self updateVoiceSearchBarVisibilityAnimated:NO];
4983 [self updateToolbar];
4984 [_infoBarContainer->view() setHidden:NO];
4985 } else {
4986 // Hide UI accessories such as find bar and first visit overlays
4987 // for welcome page.
4988 [self hideFindBarWithAnimation:NO];
4989 [_infoBarContainer->view() setHidden:YES];
4990 [_voiceSearchBar setHidden:YES];
4991 }
4992}
4993
4994- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4995 BOOL seenToolbar = NO;
4996 BOOL seenInfoBarContainer = NO;
4997 BOOL seenContentArea = NO;
4998 for (UIView* view in views.subviews) {
4999 if (view == [_toolbarController view])
5000 seenToolbar = YES;
5001 else if (view == _infoBarContainer->view())
5002 seenInfoBarContainer = YES;
5003 else if (view == _contentArea)
5004 seenContentArea = YES;
5005 if ((seenToolbar && !seenInfoBarContainer) ||
5006 (seenInfoBarContainer && !seenContentArea))
5007 return NO;
5008 }
5009 return YES;
5010}
5011
5012#pragma mark - PreloadControllerDelegate methods
5013
5014- (BOOL)shouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445015 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135016}
5017
sdefresnee65fd872016-12-19 13:38:135018#pragma mark - BookmarkBridgeMethods
5019
5020// If an added or removed bookmark is the same as the current url, update the
5021// toolbar so the star highlight is kept in sync.
5022- (void)bookmarkNodeModified:(const BookmarkNode*)node {
kkhorimotob110b262017-06-01 18:38:255023 if ([_model currentTab] &&
5024 node->url() == [_model currentTab].lastCommittedURL) {
sdefresnee65fd872016-12-19 13:38:135025 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255026 }
sdefresnee65fd872016-12-19 13:38:135027}
5028
5029// If all bookmarks are removed, update the toolbar so the star highlight is
5030// kept in sync.
5031- (void)allBookmarksRemoved {
5032 [self updateToolbar];
5033}
5034
5035#pragma mark - ShareToDelegate methods
5036
5037- (void)shareDidComplete:(ShareTo::ShareResult)shareStatus
jife5fcd332017-03-16 15:14:585038 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:135039 // The shareTo dialog dismisses itself instead of through
5040 // |-dismissViewControllerAnimated:completion:| so we must reset the
5041 // presenting state here.
5042 self.presenting = NO;
5043 [self.dialogPresenter tryToPresent];
5044
5045 switch (shareStatus) {
5046 case ShareTo::SHARE_SUCCESS:
pinkerton07e27842017-03-02 15:29:025047 if ([message length]) {
5048 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:135049 [self showSnackbar:message];
pinkerton07e27842017-03-02 15:29:025050 }
sdefresnee65fd872016-12-19 13:38:135051 break;
5052 case ShareTo::SHARE_ERROR:
5053 [self showErrorAlert:IDS_IOS_SHARE_TO_ERROR_ALERT_TITLE
5054 message:IDS_IOS_SHARE_TO_ERROR_ALERT];
5055 break;
5056 case ShareTo::SHARE_NETWORK_FAILURE:
5057 [self showErrorAlert:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT_TITLE
5058 message:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT];
5059 break;
5060 case ShareTo::SHARE_SIGN_IN_FAILURE:
5061 [self showErrorAlert:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT_TITLE
5062 message:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT];
5063 break;
5064 case ShareTo::SHARE_CANCEL:
5065 case ShareTo::SHARE_UNKNOWN_RESULT:
5066 break;
5067 }
5068}
5069
5070- (void)passwordAppExDidFinish:(ShareTo::ShareResult)shareStatus
5071 username:(NSString*)username
5072 password:(NSString*)password
jife5fcd332017-03-16 15:14:585073 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:135074 switch (shareStatus) {
5075 case ShareTo::SHARE_SUCCESS: {
5076 PasswordController* passwordController =
5077 [[_model currentTab] passwordController];
5078 __block BOOL shown = NO;
5079 [passwordController findAndFillPasswordForms:username
5080 password:password
5081 completionHandler:^(BOOL completed) {
5082 if (shown || !completed || ![message length])
5083 return;
pinkerton07e27842017-03-02 15:29:025084 TriggerHapticFeedbackForNotification(
5085 UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:135086 [self showSnackbar:message];
5087 shown = YES;
5088 }];
5089 break;
5090 }
5091 default:
5092 break;
5093 }
5094}
5095
5096- (void)showErrorAlert:(int)titleMessageId message:(int)messageId {
5097 NSString* title = l10n_util::GetNSString(titleMessageId);
5098 NSString* message = l10n_util::GetNSString(messageId);
5099 [self showErrorAlertWithStringTitle:title message:message];
5100}
5101
5102- (void)showErrorAlertWithStringTitle:(NSString*)title
5103 message:(NSString*)message {
5104 // Dismiss current alert.
5105 [_alertCoordinator stop];
5106
stkhapuginc9eee7b2017-04-10 15:49:275107 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5108 message:message
5109 viewController:self];
sdefresnee65fd872016-12-19 13:38:135110 [_alertCoordinator start];
5111}
5112
5113- (void)showSnackbar:(NSString*)message {
5114 [_dependencyFactory showSnackbarWithMessage:message];
5115}
5116
5117#pragma mark - Show Mail Composer methods
5118
5119- (void)showMailComposer:(id)sender {
5120 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
5121 if (![MFMailComposeViewController canSendMail]) {
5122 NSString* alertTitle =
5123 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
5124 NSString* alertMessage =
5125 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
5126 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5127 return;
5128 }
stkhapuginc9eee7b2017-04-10 15:49:275129 MFMailComposeViewController* mailViewController =
5130 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135131 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
5132 [mailViewController setToRecipients:[command toRecipients]];
5133 [mailViewController setSubject:[command subject]];
5134 [mailViewController setMessageBody:[command body] isHTML:NO];
5135
5136 const base::FilePath& textFile = [command textFileToAttach];
5137 if (!textFile.empty()) {
5138 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5139 NSData* data = [NSData dataWithContentsOfFile:filename];
5140 if (data) {
5141 NSString* displayName =
5142 base::SysUTF8ToNSString(textFile.BaseName().value());
5143 [mailViewController addAttachmentData:data
5144 mimeType:@"text/plain"
5145 fileName:displayName];
5146 }
5147 }
5148
5149 [mailViewController setMailComposeDelegate:self];
5150 [self presentViewController:mailViewController animated:YES completion:nil];
5151}
5152
5153#pragma mark - MFMailComposeViewControllerDelegate methods
5154
5155- (void)mailComposeController:(MFMailComposeViewController*)controller
5156 didFinishWithResult:(MFMailComposeResult)result
5157 error:(NSError*)error {
5158 [self dismissViewControllerAnimated:YES completion:nil];
5159}
5160
5161#pragma mark - StoreKitLauncher methods
5162
5163- (void)productViewControllerDidFinish:
5164 (SKStoreProductViewController*)viewController {
5165 [self dismissViewControllerAnimated:YES completion:nil];
5166}
5167
5168- (void)openAppStore:(NSString*)appId {
5169 if (![appId length])
5170 return;
5171 NSDictionary* product =
5172 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275173 SKStoreProductViewController* storeViewController =
5174 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135175 [storeViewController setDelegate:self];
5176 [storeViewController loadProductWithParameters:product completionBlock:nil];
5177 [self presentViewController:storeViewController animated:YES completion:nil];
5178}
5179
5180#pragma mark - TabDialogDelegate methods
5181
sdefresnee65fd872016-12-19 13:38:135182- (void)cancelDialogForTab:(Tab*)tab {
5183 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5184}
5185
5186#pragma mark - FKFeedbackPromptDelegate methods
5187
5188- (void)userTappedRateApp:(UIView*)view {
5189 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275190 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135191}
5192
5193- (void)userTappedSendFeedback:(UIView*)view {
5194 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275195 _rateThisAppDialog = nil;
5196 GenericChromeCommand* command =
5197 [[GenericChromeCommand alloc] initWithTag:IDC_REPORT_AN_ISSUE];
sdefresnee65fd872016-12-19 13:38:135198 [self chromeExecuteCommand:command];
5199}
5200
5201- (void)userTappedDismiss:(UIView*)view {
5202 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275203 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135204}
5205
5206#pragma mark - VoiceSearchBarDelegate
5207
5208- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275209 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135210 [self ensureVoiceSearchControllerCreated];
5211 return _voiceSearchController->IsTextToSpeechEnabled() &&
5212 _voiceSearchController->IsTextToSpeechSupported();
5213}
5214
5215- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275216 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135217 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5218}
5219
5220#pragma mark - VoiceSearchPresenter
5221
5222- (UIView*)voiceSearchButton {
5223 return _voiceSearchButton;
5224}
5225
5226- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5227 return [self currentLogoAnimationControllerOwner];
5228}
5229
5230@end