blob: 83513d14823aefbee845c27c18342b1700d6507d [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"
24#include "base/ios/weak_nsobject.h"
25#include "base/logging.h"
26#include "base/mac/bind_objc_block.h"
27#include "base/mac/bundle_locations.h"
28#include "base/mac/foundation_util.h"
29#include "base/mac/objc_property_releaser.h"
30#import "base/mac/scoped_block.h"
31#import "base/mac/scoped_nsobject.h"
32#include "base/macros.h"
33#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2934#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1335#include "base/metrics/user_metrics.h"
36#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1337#include "base/strings/sys_string_conversions.h"
tzik14236032017-02-15 06:41:0138#include "base/threading/sequenced_worker_pool.h"
sdefresnee65fd872016-12-19 13:38:1339#include "components/bookmarks/browser/base_bookmark_model_observer.h"
40#include "components/bookmarks/browser/bookmark_model.h"
gambardbdc07cc2017-02-03 16:43:1141#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1342#include "components/infobars/core/infobar_manager.h"
43#include "components/prefs/pref_service.h"
44#include "components/reading_list/core/reading_list_switches.h"
45#include "components/reading_list/ios/reading_list_model.h"
46#include "components/search_engines/search_engines_pref_names.h"
47#include "components/search_engines/template_url_service.h"
48#include "components/sessions/core/tab_restore_service_helper.h"
49#include "components/strings/grit/components_strings.h"
50#include "components/toolbar/toolbar_model_impl.h"
51#include "ios/chrome/app/tests_hook.h"
52#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
53#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
54#include "ios/chrome/browser/chrome_url_constants.h"
55#include "ios/chrome/browser/chrome_url_util.h"
gambardd2e44fb2017-01-25 09:14:2156#import "ios/chrome/browser/content_suggestions/content_suggestions_coordinator.h"
sdefresnee65fd872016-12-19 13:38:1357#include "ios/chrome/browser/experimental_flags.h"
58#import "ios/chrome/browser/favicon/favicon_loader.h"
59#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
60#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
61#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3662#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1363#include "ios/chrome/browser/first_run/first_run.h"
64#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
65#include "ios/chrome/browser/infobars/infobar_container_ios.h"
66#include "ios/chrome/browser/infobars/infobar_container_view.h"
67#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
68#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
69#import "ios/chrome/browser/native_app_launcher/native_app_navigation_controller.h"
70#import "ios/chrome/browser/open_url_util.h"
71#import "ios/chrome/browser/passwords/password_controller.h"
72#import "ios/chrome/browser/payments/payment_request_manager.h"
73#include "ios/chrome/browser/pref_names.h"
olivierrobin013ba672017-03-01 21:16:2474#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1375#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
76#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
77#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
78#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
79#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
80#import "ios/chrome/browser/snapshots/snapshot_cache.h"
81#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
82#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
pkld6e73e52017-03-08 15:56:5183#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:2884#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1385#import "ios/chrome/browser/tabs/tab.h"
86#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:1987#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:1388#import "ios/chrome/browser/tabs/tab_model.h"
89#import "ios/chrome/browser/tabs/tab_model_observer.h"
90#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
jif4a8cf942017-02-03 12:05:2491#import "ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h"
sdefresnee65fd872016-12-19 13:38:1392#import "ios/chrome/browser/ui/activity_services/share_protocol.h"
93#import "ios/chrome/browser/ui/activity_services/share_to_data.h"
jif4a8cf942017-02-03 12:05:2494#import "ios/chrome/browser/ui/activity_services/share_to_data_builder.h"
sdefresnee65fd872016-12-19 13:38:1395#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
96#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
97#import "ios/chrome/browser/ui/background_generator.h"
98#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
99#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13100#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
101#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
102#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
103#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
104#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
105#import "ios/chrome/browser/ui/commands/open_url_command.h"
106#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
107#import "ios/chrome/browser/ui/commands/show_mail_composer_command.h"
108#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
109#import "ios/chrome/browser/ui/contextual_search/contextual_search_controller.h"
110#import "ios/chrome/browser/ui/contextual_search/contextual_search_mask_view.h"
111#import "ios/chrome/browser/ui/contextual_search/contextual_search_metrics.h"
112#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_protocols.h"
113#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
114#import "ios/chrome/browser/ui/contextual_search/touch_to_search_permissions_mediator.h"
115#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
116#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
117#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
118#import "ios/chrome/browser/ui/external_file_controller.h"
119#import "ios/chrome/browser/ui/external_file_remover.h"
120#include "ios/chrome/browser/ui/file_locations.h"
121#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
122#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
123#import "ios/chrome/browser/ui/fullscreen_controller.h"
124#import "ios/chrome/browser/ui/history/tab_history_cell.h"
125#import "ios/chrome/browser/ui/key_commands_provider.h"
sdefresnee65fd872016-12-19 13:38:13126#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
127#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_panel_view_controller.h"
128#include "ios/chrome/browser/ui/omnibox/page_info_model.h"
129#import "ios/chrome/browser/ui/omnibox/page_info_view_controller.h"
130#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
131#import "ios/chrome/browser/ui/page_not_available_controller.h"
132#import "ios/chrome/browser/ui/preload_controller.h"
133#import "ios/chrome/browser/ui/preload_controller_delegate.h"
134#import "ios/chrome/browser/ui/print/print_controller.h"
135#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
136#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03137#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13138#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13139#include "ios/chrome/browser/ui/rtl_geometry.h"
140#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
141#import "ios/chrome/browser/ui/stack_view/card_view.h"
142#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
143#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
144#import "ios/chrome/browser/ui/sync/sync_util.h"
145#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
146#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
147#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
148#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
149#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
liaoyukeea9f3ee62017-03-07 22:05:39150#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13151#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
152#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
153#include "ios/chrome/browser/ui/ui_util.h"
154#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28155#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13156#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
157#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51158#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/web/error_page_content.h"
160#import "ios/chrome/browser/web/passkit_dialog_provider.h"
eugenebutcae3d9e62017-01-27 20:01:05161#import "ios/chrome/browser/web/repost_form_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13162#import "ios/chrome/browser/xcallback_parameters.h"
163#import "ios/chrome/common/material_timing.h"
164#include "ios/chrome/grit/ios_chromium_strings.h"
165#include "ios/chrome/grit/ios_strings.h"
166#import "ios/net/request_tracker.h"
167#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
168#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
169#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
170#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
171#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
172#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
173#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
174#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
sdefresnee65fd872016-12-19 13:38:13175#import "ios/web/navigation/crw_session_controller.h"
sdefresnee65fd872016-12-19 13:38:13176#include "ios/web/navigation/navigation_manager_impl.h"
177#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13178#include "ios/web/public/navigation_item.h"
179#import "ios/web/public/navigation_manager.h"
180#include "ios/web/public/referrer_util.h"
181#include "ios/web/public/ssl_status.h"
182#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39183#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13184#include "ios/web/public/web_client.h"
185#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13186#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29187#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13188#include "ios/web/public/web_state/web_state.h"
189#import "ios/web/public/web_state/web_state_delegate_bridge.h"
190#include "ios/web/public/web_thread.h"
191#import "ios/web/web_state/ui/crw_web_controller.h"
192#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17193#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13194#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
195#include "net/ssl/ssl_info.h"
196#include "net/url_request/url_request_context_getter.h"
197#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
198#include "ui/base/l10n/l10n_util.h"
199#include "ui/base/l10n/l10n_util_mac.h"
200#include "ui/base/page_transition_types.h"
201#include "url/gurl.h"
202
203using 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
272struct HeaderDefinition {
273 // The header view.
274 base::scoped_nsobject<UIView> view;
275 // How to place the view, and its behaviour when the headers move.
276 HeaderBehaviour behaviour;
277 // Reduces the height of a header to adjust for shadows.
278 CGFloat heightAdjustement;
279 // Nudges that particular header up by this number of points.
280 CGFloat inset;
281};
282
283const CGFloat kIPadFindBarOverlap = 11;
284
285bool IsURLAllowedInIncognito(const GURL& url) {
286 // Most URLs are allowed in incognito; the following are exceptions.
287 if (!url.SchemeIs(kChromeUIScheme))
288 return true;
289 std::string url_host = url.host();
290 return url_host != kChromeUIHistoryHost &&
291 url_host != kChromeUIHistoryFrameHost;
292}
293
294// Temporary key to use when storing native controllers vended to tabs before
295// they are added to the tab model.
296NSString* const kNativeControllerTemporaryKey = @"NativeControllerTemporaryKey";
297
rohitrao005a6432017-03-16 20:52:42298} // namespace
sdefresnee65fd872016-12-19 13:38:13299
300@interface BrowserViewController ()<AppRatingPromptDelegate,
301 ContextualSearchControllerDelegate,
302 ContextualSearchPanelMotionObserver,
303 CRWNativeContentProvider,
304 CRWWebStateDelegate,
305 DialogPresenterDelegate,
306 FullScreenControllerDelegate,
307 KeyCommandsPlumbing,
308 MFMailComposeViewControllerDelegate,
309 NewTabPageControllerObserver,
310 OverscrollActionsControllerDelegate,
311 PassKitDialogProvider,
312 PreloadControllerDelegate,
313 ShareToDelegate,
314 SKStoreProductViewControllerDelegate,
315 SnapshotOverlayProvider,
316 StoreKitLauncher,
317 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19318 TabHeadersDelegate,
sdefresnee65fd872016-12-19 13:38:13319 TabModelObserver,
320 TabSnapshottingDelegate,
321 UIGestureRecognizerDelegate,
322 UpgradeCenterClientProtocol,
323 VoiceSearchBarDelegate,
324 VoiceSearchBarOwner> {
325 // The dependency factory passed on initialization. Used to vend objects used
326 // by the BVC.
327 base::scoped_nsobject<BrowserViewControllerDependencyFactory>
328 _dependencyFactory;
329
330 // The browser's tab model.
331 base::scoped_nsobject<TabModel> _model;
332
333 // Facade objects used by |_toolbarController|.
334 // Must outlive |_toolbarController|.
335 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
336 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
337
338 // Preload controller. Must outlive |_toolbarController|.
339 base::scoped_nsobject<PreloadController> _preloadController;
340
341 // The WebToolbarController used to display the omnibox.
342 base::scoped_nsobject<WebToolbarController> _toolbarController;
343
344 // Controller for edge swipe gestures for page and tab navigation.
345 base::scoped_nsobject<SideSwipeController> _sideSwipeController;
346
347 // Handles displaying the context menu for all form factors.
348 base::scoped_nsobject<ContextMenuCoordinator> _contextMenuCoordinator;
349
350 // Backing object for property of the same name.
351 base::scoped_nsobject<DialogPresenter> _dialogPresenter;
352
353 // Handles presentation of JavaScript dialogs.
354 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
355
356 // Keyboard commands provider. It offloads most of the keyboard commands
357 // management off of the BVC.
358 base::scoped_nsobject<KeyCommandsProvider> _keyCommandsProvider;
359
360 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
361 // to |-reparentToolbarController| will reset it to NO.
362 BOOL _isToolbarControllerRelinquished;
363
364 // The controller that owns the currently relinquished toolbar controller.
365 // The reference is weak because it's possible for the toolbar owner to be
366 // deallocated mid-animation due to memory pressure or a tab being closed
367 // before the animation is finished.
368 base::WeakNSObject<id> _relinquishedToolbarOwner;
369
370 // Always present on tablet; always nil on phone.
371 base::scoped_nsobject<TabStripController> _tabStripController;
372
373 // The contextual search controller.
374 base::scoped_nsobject<ContextualSearchController> _contextualSearchController;
375
376 // The contextual search panel (always a subview of |self.view| if it exists).
377 ContextualSearchPanelView* _contextualSearchPanel;
378
379 // The contextual search mask (always a subview of |self.view| if it exists).
380 ContextualSearchMaskView* _contextualSearchMask;
381
382 // Used to inject Javascript implementing the PaymentRequest API and to
383 // display the UI.
384 base::scoped_nsobject<PaymentRequestManager> _paymentRequestManager;
385
386 // Used to display the Page Info UI. Nil if not visible.
387 base::scoped_nsobject<PageInfoViewController> _pageInfoController;
388
389 // Used to display the Voice Search UI. Nil if not visible.
390 scoped_refptr<VoiceSearchController> _voiceSearchController;
391
392 // Used to display the QR Scanner UI. Nil if not visible.
393 base::scoped_nsobject<QRScannerViewController> _qrScannerViewController;
394
gambard6299cc1d2017-02-21 13:06:03395 // Used to display the Reading List.
396 base::scoped_nsobject<ReadingListCoordinator> _readingListCoordinator;
397
gambardd2e44fb2017-01-25 09:14:21398 // Used to display the Suggestions.
399 base::scoped_nsobject<ContentSuggestionsCoordinator>
400 _contentSuggestionsCoordinator;
401
sdefresnee65fd872016-12-19 13:38:13402 // Used to display the Find In Page UI. Nil if not visible.
403 base::scoped_nsobject<FindBarControllerIOS> _findBarController;
404
sdefresnee65fd872016-12-19 13:38:13405 // Used to display the Print UI. Nil if not visible.
406 base::scoped_nsobject<PrintController> _printController;
407
408 // Records the set of domains for which full screen alert has already been
409 // shown.
410 base::scoped_nsobject<NSMutableSet> _fullScreenAlertShown;
411
412 // Adapter to let BVC be the delegate for WebState.
413 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
414
415 // YES if new tab is animating in.
416 BOOL _inNewTabAnimation;
417
418 // YES if Voice Search should be started when the new tab animation is
419 // finished.
420 BOOL _startVoiceSearchAfterNewTabAnimation;
421
422 // YES if the user interacts with the location bar.
423 BOOL _locationBarHasFocus;
424 // YES if a load was cancelled due to typing in the location bar.
425 BOOL _locationBarEditCancelledLoad;
426 // YES if waiting for a foreground tab due to expectNewForegroundTab.
427 BOOL _expectingForegroundTab;
428
429 // The ChromeBrowserState associated with this BVC.
430 ios::ChromeBrowserState* _browserState; // weak
431
432 // Whether or not Incognito* is enabled.
433 BOOL _isOffTheRecord;
434
435 // The last point within |_contentArea| that's received a touch.
436 CGPoint _lastTapPoint;
437
438 // The time at which |_lastTapPoint| was most recently set.
439 CFTimeInterval _lastTapTime;
440
441 // A single infobar container handles all infobars in all tabs. It keeps
442 // track of infobars for current tab (accessed via infobar helper of
443 // the current tab).
444 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
445
446 // Bridge class to deliver container change notifications to BVC.
447 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
448
449 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37450 // when displaying voice search results.
sdefresnee65fd872016-12-19 13:38:13451 base::scoped_nsprotocol<UIView<VoiceSearchBar>*> _voiceSearchBar;
452
453 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11454 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13455
456 // Card side swipe view.
457 base::scoped_nsobject<CardSideSwipeView> _sideSwipeView;
458
sdefresnee65fd872016-12-19 13:38:13459 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
460 base::scoped_nsobject<NSMutableDictionary> _dominantColorCache;
461
462 // Bridge to register for bookmark changes.
463 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
464
465 // Cached pointer to the bookmarks model.
466 bookmarks::BookmarkModel* _bookmarkModel; // weak
467
468 // The controller that shows the bookmarking UI after the user taps the star
469 // button.
470 base::scoped_nsobject<BookmarkInteractionController>
471 _bookmarkInteractionController;
472
473 // Used to remove unreferenced external files.
474 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
475
476 // The currently displayed "Rate This App" dialog, if one exists.
477 base::scoped_nsprotocol<id<AppRatingPrompt>> _rateThisAppDialog;
478
479 // Maps tab IDs to the most recent native content controller vended to that
480 // tab's web controller.
481 base::scoped_nsobject<NSMapTable> _nativeControllersForTabIDs;
482
483 // Notifies the toolbar menu of reading list changes.
484 base::scoped_nsobject<ReadingListMenuNotifier> _readingListMenuNotifier;
485
486 // The sender for the last received IDC_VOICE_SEARCH command.
487 base::WeakNSObject<UIView> _voiceSearchButton;
488
489 // Coordinator for displaying alerts.
490 base::scoped_nsobject<AlertCoordinator> _alertCoordinator;
491
492 // Releaser for properties that aren't backed by scoped_nsobjects.
493 base::mac::ObjCPropertyReleaser _propertyReleaser_BrowserViewController;
494}
495
496// The browser's side swipe controller. Lazily instantiated on the first call.
497@property(nonatomic, retain, readonly) SideSwipeController* sideSwipeController;
498// The browser's preload controller.
499@property(nonatomic, retain, readonly) PreloadController* preloadController;
500// The dialog presenter for this BVC's tab model.
501@property(nonatomic, retain, readonly) DialogPresenter* dialogPresenter;
502// The object that manages keyboard commands on behalf of the BVC.
503@property(nonatomic, retain, readonly) KeyCommandsProvider* keyCommandsProvider;
504// Whether the current tab can enable the reader mode menu item.
505@property(nonatomic, assign, readonly) BOOL canUseReaderMode;
506// Whether the current tab can enable the request desktop menu item.
507@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
508// Whether the sharing menu should be enabled.
509@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
510// Helper method to check web controller canShowFindBar method.
511@property(nonatomic, assign, readonly) BOOL canShowFindBar;
512// Whether the controller's view is currently available.
513// YES from viewWillAppear to viewWillDisappear.
514@property(nonatomic, assign, getter=isVisible) BOOL visible;
515// Whether the controller's view is currently visible.
516// YES from viewDidAppear to viewWillDisappear.
517@property(nonatomic, assign) BOOL viewVisible;
518// Whether the controller is currently dismissing a presented view controller.
519@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
520// Returns YES if the toolbar has not been scrolled out by fullscreen.
521@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
522 BOOL toolbarOnScreen;
523// Whether a new tab animation is occurring.
524@property(nonatomic, assign, readonly, getter=isInNewTabAnimation)
525 BOOL inNewTabAnimation;
526// Whether BVC prefers to hide the status bar. This value is used to determine
527// the response from the |prefersStatusBarHidden| method.
528@property(nonatomic, assign) BOOL hideStatusBar;
529// Whether the VoiceSearchBar should be displayed.
530@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
531// Coordinator for displaying a modal overlay with activity indicator to prevent
532// the user from interacting with the browser view.
533@property(nonatomic, retain)
534 ActivityOverlayCoordinator* activityOverlayCoordinator;
535
liaoyukeea9f3ee62017-03-07 22:05:39536// The user agent type used to load the currently visible page. User agent type
537// is NONE if there is no visible page or visible page is a native page.
538@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
539
sdefresnee65fd872016-12-19 13:38:13540// BVC initialization:
541// If the BVC is initialized with a valid browser state & tab model immediately,
542// the path is straightforward: functionality is enabled, and the UI is built
543// when -viewDidLoad is called.
544// If the BVC is initialized without a browser state or tab model, the tab model
545// and browser state may or may not be provided before -viewDidLoad is called.
546// In most cases, they will not, to improve startup performance.
547// In order to handle this, initialization of various aspects of BVC have been
548// broken out into the following functions, which have expectations (enforced
549// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
550
551// Registers for notifications.
552- (void)registerForNotifications;
553// Called when a tab is starting to load. If it's a link click or form
554// submission, the user is navigating away from any entries in the forward
555// history. Tell the toolbar so it can update the UI appropriately.
556// See the warning on [Tab webWillStartLoadingURL] about invocation of this
557// method sequence by malicious pages.
558- (void)pageLoadStarting:(NSNotification*)notify;
559// Called when a tab actually starts loading.
560- (void)pageLoadStarted:(NSNotification*)notify;
561// Called when a tab finishes loading. Update the Omnibox with the url and
562// stop any page load progess display.
563- (void)pageLoadComplete:(NSNotification*)notify;
564// Called when a tab is deselected in the model.
565// This notification also occurs when a tab is closed.
566- (void)tabDeselected:(NSNotification*)notify;
567// Animates sliding current tab and rotate-entering new tab while new tab loads
568// in background on the iPhone only.
569- (void)tabWasAdded:(NSNotification*)notify;
570
571// Updates non-view-related functionality with the given browser state and tab
572// model.
573// Does not matter whether or not the view has been loaded.
574- (void)updateWithTabModel:(TabModel*)model
575 browserState:(ios::ChromeBrowserState*)browserState;
576// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
577// the status bar to mimic this layout.
578- (void)installFakeStatusBar;
579// Builds the UI parts of tab strip and the toolbar. Does not matter whether
580// or not browser state and tab model are valid.
581- (void)buildToolbarAndTabStrip;
582// Updates view-related functionality with the given tab model and browser
583// state. The view must have been loaded. Uses |_browserState| and |_model|.
584- (void)addUIFunctionalityForModelAndBrowserState;
585// Sets the correct frame and heirarchy for subviews and helper views.
586- (void)setUpViewLayout;
587// Sets the correct frame for the tab strip based on the given maximum width.
588- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
589// Makes |tab| the currently visible tab, displaying its view. Calls
590// -selectedTabChanged on the toolbar only if |newSelection| is YES.
591- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
592// Initializes the bookmark interaction controller if not already initialized.
593- (void)initializeBookmarkInteractionController;
594
sdefresnee65fd872016-12-19 13:38:13595// Shows the tools menu popup.
596- (void)showToolsMenuPopup;
597// Add all delegates to the provided |tab|.
598- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14599// Remove delegates from the provided |tab|.
600- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13601// Closes the current tab, with animation if applicable.
602- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13603// Shows the menu to initiate sharing |data|.
604- (void)sharePageWithData:(ShareToData*)data;
605// Convenience method to share the current page.
606- (void)sharePage;
607// Prints the web page in the current tab.
608- (void)print;
609// Shows the Online Help Page in a tab.
610- (void)showHelpPage;
611// Show the bookmarks page.
612- (void)showAllBookmarks;
613// Shows a panel within the New Tab Page.
614- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel;
615// Shows the "rate this app" dialog.
616- (void)showRateThisAppDialog;
617// Dismisses the "rate this app" dialog.
618- (void)dismissRateThisAppDialog;
619#if !defined(NDEBUG)
620// Shows the source of the current page.
621- (void)viewSource;
622#endif
olivierrobin889af53f2017-03-01 14:56:32623// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13624- (BOOL)isTabNativePage:(Tab*)tab;
625// Returns the view to use when animating a page in or out, positioning it to
626// fill the content area but not actually adding it to the view hierarchy.
627- (UIImageView*)pageOpenCloseAnimationView;
628// Returns the view to use when animating full screen NTP paper in, filling the
629// entire screen but not actually adding it to the view hierarchy.
630- (UIImageView*)pageFullScreenOpenCloseAnimationView;
631// Updates the toolbar display based on the current tab.
632- (void)updateToolbar;
633// Updates |dialogPresenter|'s |active| property to account for the BVC's
634// |active| and |visible| properties.
635- (void)updateDialogPresenterActiveState;
636// Dismisses popups and modal dialogs that are displayed above the BVC upon size
637// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
638// is performed.
639// TODO(crbug.com/522721): Support size changes for all popups and modal
640// dialogs.
641- (void)dismissPopups;
642// Create and show the find bar.
643- (void)initFindBarForTab;
644// Search for find bar query string.
645- (void)searchFindInPage;
646// Update find bar with model data. If |shouldFocus| is set to YES, the text
647// field will become first responder.
648- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
649// Close and disable find in page bar.
650- (void)closeFindInPage;
651// Hide find bar.
652- (void)hideFindBarWithAnimation:(BOOL)animate;
653// Shows find bar. If |selectText| is YES, all text inside the Find Bar
654// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
655// set to be first responder.
656- (void)showFindBarWithAnimation:(BOOL)animate
657 selectText:(BOOL)selectText
658 shouldFocus:(BOOL)shouldFocus;
659// Show the Page Security Info.
660- (void)showPageInfoPopupForView:(UIView*)sourceView;
661// Hide the Page Security Info.
662- (void)hidePageInfoPopupForView:(UIView*)sourceView;
663// Shows the tab history popup containing the tab's backward history.
664- (void)showTabHistoryPopupForBackwardHistory;
665// Shows the tab history popup containing the tab's forward history.
666- (void)showTabHistoryPopupForForwardHistory;
667// Navigate back/forward to the selected entry in the tab's history.
668- (void)navigateToSelectedEntry:(id)sender;
669// The infobar state (typically height) has changed.
670- (void)infoBarContainerStateChanged:(bool)is_animating;
671// Adds a CardView on top of the contentArea either taking the size of the full
672// screen or just the size of the space under the header.
673// Returns the CardView that was added.
674- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
675// Called when either a tab finishes loading or when a tab with finished content
676// is added directly to the model via pre-rendering. The tab must be non-nil and
677// must be a member of the tab model controlled by this BrowserViewController.
678- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
679// Evaluates Javascript asynchronously using the current page context.
680- (void)openJavascript:(NSString*)javascript;
liaoyukeea9f3ee62017-03-07 22:05:39681
682// Sets the desktop user agent flag and reloads the current page.
sdefresnee65fd872016-12-19 13:38:13683- (void)enableDesktopUserAgent;
liaoyukeea9f3ee62017-03-07 22:05:39684
685// Sets the desktop user agent flag and reloads the current page.
686- (void)enableMobileUserAgent;
687
sdefresnee65fd872016-12-19 13:38:13688// Helper methods used by ShareToDelegate methods.
689// Shows an alert with the given title and message id.
690- (void)showErrorAlert:(int)titleMessageId message:(int)messageId;
691// Helper method displaying an alert with the given title and message.
692// Dismisses previous alert if it has not been dismissed yet.
693- (void)showErrorAlertWithStringTitle:(NSString*)title
694 message:(NSString*)message;
695// Shows a self-dismissing snackbar displaying |message|.
696- (void)showSnackbar:(NSString*)message;
697// Induces an intentional crash in the browser process.
698- (void)induceBrowserCrash;
699// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17700- (void)managePermissionAndSaveImage:(NSData*)data
701 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13702// Saves the image. In order to keep the metadata of the image, the image is
703// saved as a temporary file on disk then saved in photos.
704// This should be called on FILE thread.
gambard9efce7a2017-02-09 18:53:17705- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13706// Called when Chrome has been denied access to the photos or videos and the
707// user can change it.
708// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
709// settings. Dismiss previous alert if it has not been dismissed yet.
710- (void)displayImageErrorAlertWithSettingsOnMainQueue;
711// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
712// previous alert if it has not been dismissed yet.
713- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
714// Called when Chrome has been denied access to the photos or videos and the
715// user cannot change it.
716// Shows a privacy alert on the main queue, with errorContent as the message.
717// Dismisses previous alert if it has not been dismissed yet.
718- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
719// Called with the results of saving a picture in the photo album. If error is
720// nil the save succeeded.
721- (void)finishSavingImageWithError:(NSError*)error;
722// Provides a view that encompasses currently displayed infobar(s) or nil
723// if no infobar is presented.
724- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
725// Returns a vertical infobar offset relative to the tab content.
726- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
727// Provides a view that encompasses the voice search bar if it's displayed or
728// nil if the voice search bar isn't displayed.
729- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
730// Returns a vertical voice search bar offset relative to the tab content.
731- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
732// Lazily instantiates |_voiceSearchController|.
733- (void)ensureVoiceSearchControllerCreated;
734// Lazily instantiates |_voiceSearchBar| and adds it to the view.
735- (void)ensureVoiceSearchBarCreated;
736// Shows/hides the voice search bar.
737- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
738// The LogoAnimationControllerOwner to be used for the next logo transition
739// animation.
740- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
741// Returns the header views, all the chrome on top of the page, including the
742// ones that cannot be scrolled off screen by full screen.
743- (const std::vector<HeaderDefinition>)headerViews;
744// Returns the footer view if one exists (e.g. the voice search bar).
745- (UIView*)footerView;
746// Returns the height of the header view for the tab model's current tab.
747- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13748// Sets the frame for the headers.
749- (void)setFramesForHeaders:(const std::vector<HeaderDefinition>)headers
750 atOffset:(CGFloat)headerOffset;
751// Returns the y coordinate for the footer's frame when animating the footer
752// in/out of fullscreen.
753- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
754// Called when the animation for setting the header view's offset is finished.
755// |completed| should indicate if the animation finished completely or was
756// interrupted. |offset| should indicate the header offset after the animation.
757// |dragged| should indicate if the header moved due to the user dragging.
758- (void)fullScreenController:(FullScreenController*)controller
759 headerAnimationCompleted:(BOOL)completed
760 offset:(CGFloat)offset;
761// Performs a search with the image at the given url. The referrer is used to
762// download the image.
763- (void)searchByImageAtURL:(const GURL&)url
764 referrer:(const web::Referrer)referrer;
765// Saves the image at the given URL on the system's album. The referrer is used
766// to download the image.
767- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
768
769// Determines the center of |sender| if it's a view or a toolbar item, and save
770// the CGPoint and timestamp.
771- (void)setLastTapPoint:(id)sender;
772// Get return the last stored |_lastTapPoint| if it's been set within the past
773// second.
774- (CGPoint)lastTapPoint;
775// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
776- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
777// Returns the native controller being used by |tab|'s web controller.
778- (id)nativeControllerForTab:(Tab*)tab;
779// Installs the BVC as overscroll actions controller of |nativeContent| if
780// needed. Sets the style of the overscroll actions toolbar.
781- (void)setOverScrollActionControllerToStaticNativeContent:
782 (StaticHtmlNativeContent*)nativeContent;
783// Whether the BVC should declare keyboard commands.
784- (BOOL)shouldRegisterKeyboardCommands;
785// Adds the given url to the reading list.
786- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
787@end
788
789class InfoBarContainerDelegateIOS
790 : public infobars::InfoBarContainer::Delegate {
791 public:
792 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
793 : controller_(controller) {}
794
795 ~InfoBarContainerDelegateIOS() override {}
796
797 private:
798 SkColor GetInfoBarSeparatorColor() const override {
799 NOTIMPLEMENTED();
800 return SK_ColorBLACK;
801 }
802
803 int ArrowTargetHeightForInfoBar(
804 size_t index,
805 const gfx::SlideAnimation& animation) const override {
806 return 0;
807 }
808
809 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
810 int arrow_target_height,
811 int bar_target_height,
812 int* arrow_height,
813 int* arrow_half_width,
814 int* bar_height) const override {
815 DCHECK_NE(-1, bar_target_height)
816 << "Infobars don't have a default height on iOS";
817 *arrow_height = 0;
818 *arrow_half_width = 0;
819 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
820 }
821
822 void InfoBarContainerStateChanged(bool is_animating) override {
823 [controller_ infoBarContainerStateChanged:is_animating];
824 }
825
826 bool DrawInfoBarArrows(int* x) const override { return false; }
827
828 BrowserViewController* controller_; // weak
829};
830
831// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
832@interface BrowserViewController (BookmarkBridgeMethods)
833// If a bookmark matching the currentTab url is added or moved, update the
834// toolbar state so the star highlight is in sync.
835- (void)bookmarkNodeModified:(const BookmarkNode*)node;
836- (void)allBookmarksRemoved;
837@end
838
839// Handle notification that bookmarks has been removed changed so we can update
840// the bookmarked star icon.
841class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
842 public:
843 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
844 : owner_(owner) {}
845
846 ~BrowserBookmarkModelBridge() override {}
847
848 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
849 const BookmarkNode* parent,
850 int old_index,
851 const BookmarkNode* node,
852 const std::set<GURL>& removed_urls) override {
853 [owner_ bookmarkNodeModified:node];
854 }
855
856 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
857 bool ids_reassigned) override {}
858
859 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
860 const BookmarkNode* old_parent,
861 int old_index,
862 const BookmarkNode* new_parent,
863 int new_index) override {}
864
865 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
866 const BookmarkNode* parent,
867 int index) override {
868 [owner_ bookmarkNodeModified:parent->GetChild(index)];
869 }
870
871 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
872 const BookmarkNode* node) override {}
873
874 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
875 const BookmarkNode* node) override {}
876
877 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
878 const BookmarkNode* node) override {}
879
880 void BookmarkAllUserNodesRemoved(
881 bookmarks::BookmarkModel* model,
882 const std::set<GURL>& removed_urls) override {
883 [owner_ allBookmarksRemoved];
884 }
885
886 private:
887 BrowserViewController* owner_; // Weak.
888};
889
890@implementation BrowserViewController
891
892@synthesize contentArea = _contentArea;
893@synthesize typingShield = _typingShield;
894@synthesize active = _active;
895@synthesize visible = _visible;
896@synthesize viewVisible = _viewVisible;
897@synthesize dismissingModal = _dismissingModal;
898@synthesize hideStatusBar = _hideStatusBar;
899@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
900@synthesize presenting = _presenting;
901
902#pragma mark - Object lifecycle
903
904- (instancetype)initWithTabModel:(TabModel*)model
905 browserState:(ios::ChromeBrowserState*)browserState
906 dependencyFactory:
907 (BrowserViewControllerDependencyFactory*)factory {
908 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
909 if (self) {
910 DCHECK(factory);
911 _propertyReleaser_BrowserViewController.Init(self,
912 [BrowserViewController class]);
sdefresnee65fd872016-12-19 13:38:13913 _dependencyFactory.reset([factory retain]);
914 _nativeControllersForTabIDs.reset(
915 [[NSMapTable strongToWeakObjectsMapTable] retain]);
916 _dialogPresenter.reset([[DialogPresenter alloc] initWithDelegate:self
917 presentingViewController:self]);
918 _javaScriptDialogPresenter.reset(
919 new JavaScriptDialogPresenterImpl(_dialogPresenter));
920 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
921 // TODO(leng): Delay this.
922 [[UpgradeCenter sharedInstance] registerClient:self];
923 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13924 if (model && browserState)
925 [self updateWithTabModel:model browserState:browserState];
926 if ([[NSUserDefaults standardUserDefaults]
927 boolForKey:@"fullScreenShowAlert"]) {
928 _fullScreenAlertShown.reset([[NSMutableSet alloc] init]);
929 }
930 }
931 return self;
932}
933
934- (instancetype)initWithNibName:(NSString*)nibNameOrNil
935 bundle:(NSBundle*)nibBundleOrNil {
936 NOTREACHED();
937 return nil;
938}
939
940- (instancetype)initWithCoder:(NSCoder*)aDecoder {
941 NOTREACHED();
942 return nil;
943}
944
945- (void)dealloc {
946 _tabStripController.reset();
947 _infoBarContainer.reset();
948 _readingListMenuNotifier.reset();
sdefresnedc432f42017-01-17 14:36:59949 if (_bookmarkModel)
950 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
sdefresnee65fd872016-12-19 13:38:13951 [_model removeObserver:self];
952 [[UpgradeCenter sharedInstance] unregisterClient:self];
953 [[NSNotificationCenter defaultCenter] removeObserver:self];
954 [_toolbarController setDelegate:nil];
955 if (_voiceSearchController.get())
956 _voiceSearchController->SetDelegate(nil);
957 [_rateThisAppDialog setDelegate:nil];
958 [_model closeAllTabs];
959 [super dealloc];
960}
961
962#pragma mark - Accessibility
963
964- (BOOL)accessibilityPerformEscape {
965 [self dismissPopups];
966 return YES;
967}
968
969#pragma mark - Properties
970
sdefresnee65fd872016-12-19 13:38:13971- (void)setActive:(BOOL)active {
972 if (_active == active) {
973 return;
974 }
975 _active = active;
976
977 // If not active, display an activity indicator overlay over the view to
978 // prevent interaction with the web page.
979 // TODO(crbug.com/637093): This coordinator should be managed by the
980 // coordinator used to present BrowserViewController, when implemented.
981 if (active) {
982 [self.activityOverlayCoordinator stop];
983 self.activityOverlayCoordinator = nil;
984 } else if (!self.activityOverlayCoordinator) {
985 self.activityOverlayCoordinator = [[[ActivityOverlayCoordinator alloc]
986 initWithBaseViewController:self] autorelease];
987 [self.activityOverlayCoordinator start];
988 }
989
990 if (_browserState) {
991 web::ActiveStateManager* active_state_manager =
992 web::BrowserState::GetActiveStateManager(_browserState);
993 active_state_manager->SetActive(active);
994 }
995
996 [_model setWebUsageEnabled:active];
997 [self updateDialogPresenterActiveState];
998
999 if (active) {
1000 // Make sure the tab (if any; it's possible to get here without a current
1001 // tab if the caller is about to create one) ends up on screen completely.
1002 Tab* currentTab = [_model currentTab];
1003 // Force loading the view in case it was not loaded yet.
1004 [self ensureViewCreated];
1005 if (_expectingForegroundTab)
1006 [currentTab.webController setOverlayPreviewMode:YES];
1007 if (currentTab)
1008 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341009 } else {
1010 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131011 }
1012 [_contextualSearchController enableContextualSearch:active];
1013 [_paymentRequestManager enablePaymentRequest:active];
1014
1015 [self setNeedsStatusBarAppearanceUpdate];
1016}
1017
1018- (void)setPrimary:(BOOL)primary {
1019 [_model setPrimary:primary];
1020 if (primary) {
1021 [self updateDialogPresenterActiveState];
1022 } else {
1023 self.dialogPresenter.active = false;
1024 }
1025}
1026
1027- (BOOL)isPlayingTTS {
1028 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1029}
1030
sdefresne6165c8742017-01-16 15:42:021031- (ios::ChromeBrowserState*)browserState {
1032 return _browserState;
1033}
1034
1035- (TabModel*)tabModel {
1036 return _model.get();
1037}
1038
sdefresnee65fd872016-12-19 13:38:131039- (SideSwipeController*)sideSwipeController {
1040 if (!_sideSwipeController) {
1041 _sideSwipeController.reset([[SideSwipeController alloc]
1042 initWithTabModel:_model
1043 browserState:_browserState]);
1044 [_sideSwipeController setSnapshotDelegate:self];
1045 [_sideSwipeController setSwipeDelegate:self];
1046 }
1047 return _sideSwipeController;
1048}
1049
1050- (PreloadController*)preloadController {
1051 return _preloadController.get();
1052}
1053
1054- (DialogPresenter*)dialogPresenter {
1055 return _dialogPresenter;
1056}
1057
1058- (BOOL)canUseReaderMode {
1059 Tab* tab = [_model currentTab];
1060 if ([self isTabNativePage:tab])
1061 return NO;
1062
1063 return [tab canSwitchToReaderMode];
1064}
1065
1066- (BOOL)canUseDesktopUserAgent {
1067 Tab* tab = [_model currentTab];
1068 if ([self isTabNativePage:tab])
1069 return NO;
1070
1071 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441072 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131073}
1074
1075// Whether the sharing menu should be shown.
1076- (BOOL)canShowShareMenu {
1077 Tab* tab = [_model currentTab];
1078 // TODO(shreyasv): Make it so the URL returned by the tab is always valid and
1079 // remove check on net::NSURLWithGURL(tab.url) ( http://crbug.com/400999 ).
1080 return tab && !tab.url.SchemeIs(kChromeUIScheme) &&
1081 net::NSURLWithGURL(tab.url);
1082}
1083
1084- (BOOL)canShowFindBar {
1085 // Make sure web controller can handle find in page.
1086 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421087 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131088 return NO;
rohitrao005a6432017-03-16 20:52:421089 }
sdefresnee65fd872016-12-19 13:38:131090
rohitrao005a6432017-03-16 20:52:421091 auto* helper = FindTabHelper::FromWebState(tab.webState);
1092 return (helper && helper->CurrentPageSupportsFindInPage() &&
1093 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131094}
1095
liaoyukeea9f3ee62017-03-07 22:05:391096- (web::UserAgentType)userAgentType {
1097 web::WebState* webState = [_model currentTab].webState;
1098 if (!webState)
1099 return web::UserAgentType::NONE;
1100 web::NavigationItem* visibleItem =
1101 webState->GetNavigationManager()->GetVisibleItem();
1102 if (!visibleItem)
1103 return web::UserAgentType::NONE;
1104
1105 return visibleItem->GetUserAgentType();
1106}
1107
sdefresnee65fd872016-12-19 13:38:131108- (void)setVisible:(BOOL)visible {
1109 if (_visible == visible)
1110 return;
1111 _visible = visible;
1112}
1113
1114- (void)setViewVisible:(BOOL)viewVisible {
1115 if (_viewVisible == viewVisible)
1116 return;
1117 _viewVisible = viewVisible;
1118 self.visible = viewVisible;
1119 [self updateDialogPresenterActiveState];
1120}
1121
1122- (BOOL)isToolbarOnScreen {
1123 return [self headerHeight] - [self currentHeaderOffset] > 0;
1124}
1125
1126- (BOOL)isInNewTabAnimation {
1127 return _inNewTabAnimation;
1128}
1129
1130- (BOOL)shouldShowVoiceSearchBar {
1131 // On iPads, the voice search bar should only be shown for regular horizontal
1132 // size class configurations. It should always be shown for voice search
1133 // results Tabs on iPhones, including configurations with regular horizontal
1134 // size classes (i.e. landscape iPhone 6 Plus).
1135 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1136 UIUserInterfaceSizeClassCompact;
1137 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1138 (!IsIPadIdiom() || compactWidth);
1139}
1140
1141- (void)setHideStatusBar:(BOOL)hideStatusBar {
1142 if (_hideStatusBar == hideStatusBar)
1143 return;
1144 _hideStatusBar = hideStatusBar;
1145 [self setNeedsStatusBarAppearanceUpdate];
1146}
1147
1148#pragma mark - IBActions
1149
1150- (void)shieldWasTapped:(id)sender {
1151 [_toolbarController cancelOmniboxEdit];
1152}
1153
1154- (void)newTab:(id)sender {
1155 [self setLastTapPoint:sender];
1156 DCHECK(self.visible || self.dismissingModal);
1157 Tab* currentTab = [_model currentTab];
1158 if (currentTab) {
jif7fed8122017-02-08 13:15:251159 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
sdefresnee65fd872016-12-19 13:38:131160 }
1161 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1162 transition:ui::PAGE_TRANSITION_TYPED];
1163}
1164
1165#pragma mark - UIViewController methods
1166
1167// Perform additional set up after loading the view, typically from a nib.
1168- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281169 CGRect initialViewsRect = self.view.frame;
1170 initialViewsRect.origin.y += StatusBarHeight();
1171 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131172 UIViewAutoresizing initialViewAutoresizing =
1173 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1174
1175 self.contentArea = [[[BrowserContainerView alloc]
1176 initWithFrame:initialViewsRect] autorelease];
1177 self.contentArea.autoresizingMask = initialViewAutoresizing;
1178 self.typingShield =
1179 [[[UIButton alloc] initWithFrame:initialViewsRect] autorelease];
1180 self.typingShield.autoresizingMask = initialViewAutoresizing;
1181 [self.typingShield addTarget:self
1182 action:@selector(shieldWasTapped:)
1183 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131184 self.view.autoresizingMask = initialViewAutoresizing;
1185 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1186 [self.view addSubview:self.contentArea];
1187 [self.view addSubview:self.typingShield];
1188 [super viewDidLoad];
1189
1190 // Install fake status bar for iPad iOS7
1191 [self installFakeStatusBar];
1192 [self buildToolbarAndTabStrip];
1193 [self setUpViewLayout];
1194 // If the tab model and browser state are valid, finish initialization.
1195 if (_model && _browserState)
1196 [self addUIFunctionalityForModelAndBrowserState];
1197
1198 // Add a tap gesture recognizer to save the last tap location for the source
1199 // location of the new tab animation.
1200 base::scoped_nsobject<UITapGestureRecognizer> tapRecognizer(
1201 [[UITapGestureRecognizer alloc]
1202 initWithTarget:self
1203 action:@selector(saveContentAreaTapLocation:)]);
1204 [tapRecognizer setDelegate:self];
1205 [tapRecognizer setCancelsTouchesInView:NO];
1206 [_contentArea addGestureRecognizer:tapRecognizer];
1207}
1208
1209- (void)viewDidAppear:(BOOL)animated {
1210 [super viewDidAppear:animated];
1211 self.viewVisible = YES;
1212 [self updateDialogPresenterActiveState];
1213}
1214
1215- (void)viewWillAppear:(BOOL)animated {
1216 [super viewWillAppear:animated];
1217
1218 // Reparent the toolbar if it's been relinquished.
1219 if (_isToolbarControllerRelinquished)
1220 [self reparentToolbarController];
1221
1222 self.visible = YES;
1223
1224 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251225 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131226 _infoBarContainer->RestoreInfobars();
1227 }
1228
1229 // If the controller is suspended, or has been paged out due to low memory,
1230 // updating the view will be handled when it's displayed again.
1231 if (![_model webUsageEnabled] || !self.contentArea)
1232 return;
1233 // Update the displayed tab (if any; the switcher may not have created one
1234 // yet) in case it changed while showing the switcher.
1235 Tab* currentTab = [_model currentTab];
1236 if (currentTab)
1237 [self displayTab:currentTab isNewSelection:YES];
1238}
1239
1240- (void)viewWillDisappear:(BOOL)animated {
1241 self.viewVisible = NO;
1242 [self updateDialogPresenterActiveState];
1243 [[_model currentTab] updateFullscreenWithToolbarVisible:YES];
1244 [[_model currentTab] wasHidden];
1245 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251246 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131247 _infoBarContainer->SuspendInfobars();
1248 }
1249 [super viewWillDisappear:animated];
1250}
1251
1252- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1253 duration:(NSTimeInterval)duration {
1254 [super willRotateToInterfaceOrientation:orient duration:duration];
1255 [self dismissPopups];
1256 [self reshowFindBarIfNeededWithCoordinator:nil];
1257}
1258
1259- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1260 [super didRotateFromInterfaceOrientation:orient];
1261
1262 // This reinitializes the toolbar, including updating the Overlay View,
1263 // if there is one.
1264 [self updateToolbar];
1265 [self infoBarContainerStateChanged:false];
1266}
1267
1268- (BOOL)prefersStatusBarHidden {
1269 return self.hideStatusBar;
1270}
1271
1272// Called when in the foreground and the OS needs more memory. Release as much
1273// as possible.
1274- (void)didReceiveMemoryWarning {
1275 // Releases the view if it doesn't have a superview.
1276 [super didReceiveMemoryWarning];
1277
1278 // Release any cached data, images, etc that aren't in use.
1279 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1280 // but since the FaviconCache uses obj-c in the header, it can't be included
1281 // there.
1282 if (_browserState) {
1283 FaviconLoader* loader =
1284 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1285 _browserState);
1286 if (loader)
1287 loader->PurgeCache();
1288 }
1289
1290 if (![self isViewLoaded]) {
1291 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1292 // as the BrowserViewController.
1293 self.contentArea = nil;
1294 self.typingShield = nil;
1295 if (_voiceSearchController.get())
1296 _voiceSearchController->SetDelegate(nil);
gambard6299cc1d2017-02-21 13:06:031297 _contentSuggestionsCoordinator.reset();
sdefresnee65fd872016-12-19 13:38:131298 _qrScannerViewController.reset();
gambard6299cc1d2017-02-21 13:06:031299 _readingListCoordinator.reset();
sdefresnee65fd872016-12-19 13:38:131300 _toolbarController.reset();
1301 _toolbarModelDelegate.reset();
1302 _toolbarModelIOS.reset();
1303 _tabStripController.reset();
sdefresnee65fd872016-12-19 13:38:131304 _sideSwipeController.reset();
1305 }
1306}
1307
1308- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1309 [super traitCollectionDidChange:previousTraitCollection];
1310 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1311 // because in some cases the presented view controller isn't a child of the
1312 // BVC in the view controller hierarchy (some intervening object isn't a
1313 // view controller).
1314 [self.presentedViewController
1315 traitCollectionDidChange:previousTraitCollection];
1316 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1317 // Update voice search bar visibility.
1318 [self updateVoiceSearchBarVisibilityAnimated:NO];
1319}
1320
1321- (void)viewWillTransitionToSize:(CGSize)size
1322 withTransitionCoordinator:
1323 (id<UIViewControllerTransitionCoordinator>)coordinator {
1324 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1325 [self dismissPopups];
1326 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1327}
1328
1329- (void)reshowFindBarIfNeededWithCoordinator:
1330 (id<UIViewControllerTransitionCoordinator>)coordinator {
1331 if (![_findBarController isFindInPageShown])
1332 return;
1333
1334 // Record focused state.
1335 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1336
1337 [self hideFindBarWithAnimation:NO];
1338
1339 base::WeakNSObject<BrowserViewController> weakSelf(self);
1340 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1341 id<UIViewControllerTransitionCoordinatorContext> context) {
1342 base::scoped_nsobject<BrowserViewController> strongSelf([weakSelf retain]);
1343 if (strongSelf)
1344 [strongSelf showFindBarWithAnimation:NO
1345 selectText:NO
1346 shouldFocus:isFocusedBeforeReshow];
1347 };
1348
1349 BOOL enqueued =
1350 [coordinator animateAlongsideTransition:nil completion:completion];
1351 if (!enqueued) {
1352 completion(nil);
1353 }
1354}
1355
1356- (void)dismissViewControllerAnimated:(BOOL)flag
1357 completion:(void (^)())completion {
1358 self.dismissingModal = YES;
1359 base::WeakNSObject<BrowserViewController> weakSelf(self);
1360 [super dismissViewControllerAnimated:flag
1361 completion:^{
1362 base::scoped_nsobject<BrowserViewController>
1363 strongSelf([weakSelf retain]);
1364 [strongSelf setDismissingModal:NO];
1365 [strongSelf setPresenting:NO];
1366 if (completion)
1367 completion();
1368 [[strongSelf dialogPresenter] tryToPresent];
1369 }];
1370}
1371
1372- (void)presentViewController:(UIViewController*)viewControllerToPresent
1373 animated:(BOOL)flag
1374 completion:(void (^)())completion {
1375 base::mac::ScopedBlock<ProceduralBlock> finalCompletionHandler(
1376 [completion copy]);
1377 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1378 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1379 // presented, to show a temporary view of the launch screen and then remove it
1380 // when the controller for the FRE has been presented. This fix should be
1381 // removed when the FRE startup code is rewritten.
1382 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1383 experimental_flags::AlwaysDisplayFirstRun()) &&
1384 !tests_hook::DisableFirstRun();
1385 // These if statements check that |presentViewController| is being called for
1386 // the FRE case.
1387 if (firstRunLaunch &&
1388 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1389 UINavigationController* navController =
1390 base::mac::ObjCCastStrict<UINavigationController>(
1391 viewControllerToPresent);
1392 if ([navController.topViewController
1393 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1394 self.hideStatusBar = YES;
1395
1396 // Load view from Launch Screen and add it to window.
1397 NSBundle* mainBundle = base::mac::FrameworkBundle();
1398 NSArray* topObjects =
1399 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1400 UIViewController* launchScreenController =
1401 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1402 // |launchScreenView| is loaded as an autoreleased object, and is retained
1403 // by the |completion| block below.
1404 UIView* launchScreenView = launchScreenController.view;
1405 launchScreenView.userInteractionEnabled = NO;
1406 launchScreenView.frame = self.view.window.bounds;
1407 [self.view.window addSubview:launchScreenView];
1408
1409 // Replace the completion handler sent to the superclass with one which
1410 // removes |launchScreenView| and resets the status bar. If |completion|
1411 // exists, it is called from within the new completion handler.
1412 base::WeakNSObject<BrowserViewController> weakSelf(self);
1413 finalCompletionHandler.reset([^{
1414 [launchScreenView removeFromSuperview];
1415 weakSelf.get().hideStatusBar = NO;
1416 if (completion)
1417 completion();
1418 } copy]);
1419 }
1420 }
1421
1422 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171423 if ([_sideSwipeController inSwipe]) {
1424 [_sideSwipeController resetContentView];
1425 }
sdefresnee65fd872016-12-19 13:38:131426
1427 [super presentViewController:viewControllerToPresent
1428 animated:flag
1429 completion:finalCompletionHandler];
1430}
1431
1432#pragma mark - Notification handling
1433
1434- (void)registerForNotifications {
1435 DCHECK(_model);
1436 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1437 [defaultCenter addObserver:self
1438 selector:@selector(pageLoadStarting:)
1439 name:kTabModelTabWillStartLoadingNotification
1440 object:_model];
1441 [defaultCenter addObserver:self
1442 selector:@selector(pageLoadStarted:)
1443 name:kTabModelTabDidStartLoadingNotification
1444 object:_model];
1445 [defaultCenter addObserver:self
1446 selector:@selector(pageLoadComplete:)
1447 name:kTabModelTabDidFinishLoadingNotification
1448 object:_model];
1449 [defaultCenter addObserver:self
1450 selector:@selector(tabDeselected:)
1451 name:kTabModelTabDeselectedNotification
1452 object:_model];
1453 [defaultCenter addObserver:self
1454 selector:@selector(tabWasAdded:)
1455 name:kTabModelNewTabWillOpenNotification
1456 object:_model];
1457}
1458
1459- (void)pageLoadStarting:(NSNotification*)notify {
1460 Tab* tab = notify.userInfo[kTabModelTabKey];
1461 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
1462 // Hide find bar when navigating to a new page.
1463 [self hideFindBarWithAnimation:NO];
rohitraob2bf3cb2017-02-10 14:10:361464
sdefresnee65fd872016-12-19 13:38:131465 if (tab == [_model currentTab]) {
1466 // TODO(pinkerton): Fill in here about hiding the forward button on
1467 // navigation.
1468 }
1469}
1470
1471- (void)pageLoadStarted:(NSNotification*)notify {
1472 Tab* tab = notify.userInfo[kTabModelTabKey];
1473 DCHECK(tab);
1474 if (tab == [_model currentTab]) {
1475 if (![self isTabNativePage:tab]) {
1476 [_toolbarController currentPageLoadStarted];
1477 }
1478 [self updateVoiceSearchBarVisibilityAnimated:NO];
1479 }
1480}
1481
1482- (void)pageLoadComplete:(NSNotification*)notify {
1483 // Update the UI, but only if the current tab.
1484 Tab* tab = notify.userInfo[kTabModelTabKey];
1485 if (tab == [_model currentTab]) {
1486 // There isn't any need to update the toolbar here. When the page finishes,
1487 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1488 }
1489
1490 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1491
1492 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1493}
1494
1495- (void)tabDeselected:(NSNotification*)notify {
1496 DCHECK(notify);
1497 Tab* tab = notify.userInfo[kTabModelTabKey];
1498 DCHECK(tab);
1499 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221500 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131501}
1502
1503- (void)tabWasAdded:(NSNotification*)notify {
1504 Tab* tab = notify.userInfo[kTabModelTabKey];
1505 DCHECK(tab);
1506
1507 // Update map if a native controller was vended before the tab was added.
1508 id<CRWNativeContent> nativeController =
1509 [_nativeControllersForTabIDs objectForKey:kNativeControllerTemporaryKey];
1510 if (nativeController) {
1511 [_nativeControllersForTabIDs
1512 removeObjectForKey:kNativeControllerTemporaryKey];
1513 [_nativeControllersForTabIDs setObject:nativeController forKey:tab.tabId];
1514 }
1515
1516 // When adding new tabs, check what kind of reminder infobar should
1517 // be added to the new tab. Try to add only one of them.
1518 // This check is done when a new tab is added either through the Tools Menu
1519 // "New Tab" or through "New Tab" in Stack View Controller. This method
1520 // is called after a new tab has added and finished initial navigation.
1521 // If this is added earlier, the initial navigation may end up clearing
1522 // the infobar(s) that are just added. See http://crbug/340250 for details.
1523 [[UpgradeCenter sharedInstance] addInfoBarToManager:[tab infoBarManager]
1524 forTabId:[tab tabId]];
1525 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1526 ios_internal::sync::displaySyncErrors(_browserState, tab);
1527 }
1528
1529 // The rest of this function initiates the new tab animation, which is
1530 // phone-specific.
1531 if (IsIPadIdiom())
1532 return;
1533
1534 // Do nothing if browsing is currently suspended. The BVC will set everything
1535 // up correctly when browsing resumes.
1536 if (!self.visible || ![_model webUsageEnabled])
1537 return;
1538
1539 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1540
1541 // Block that starts voice search at the end of new Tab animation if
1542 // necessary.
1543 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1544 if (_startVoiceSearchAfterNewTabAnimation) {
1545 _startVoiceSearchAfterNewTabAnimation = NO;
1546 [self startVoiceSearch];
1547 }
1548 };
1549
1550 _inNewTabAnimation = YES;
1551 if (!inBackground) {
1552 UIView* animationParentView = _contentArea;
1553 // Create the new page image, and load with the new tab page snapshot.
1554 CGFloat newPageOffset = 0;
1555 UIImageView* newPage;
1556 if (tab.url == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
1557 !IsIPadIdiom()) {
1558 animationParentView = self.view;
1559 newPage = [self pageFullScreenOpenCloseAnimationView];
1560 } else {
1561 newPage = [self pageOpenCloseAnimationView];
1562 }
1563 newPageOffset = newPage.frame.origin.y;
1564
1565 [tab view].frame = _contentArea.bounds;
1566 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1567 [animationParentView addSubview:newPage];
1568 CGPoint origin = [self lastTapPoint];
1569 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1570 newPage, -newPageOffset,
1571 newPage.frame.size.height - newPage.image.size.height, origin,
1572 _isOffTheRecord, NULL, ^{
1573 [newPage removeFromSuperview];
1574 _inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421575 // Use the model's currentTab here because it is possible that it can
1576 // be reset to a new value before the new Tab animation finished (e.g.
1577 // if another Tab shows a dialog via |dialogPresenter|). However, that
1578 // tab's view hasn't been displayed yet because it was in a new tab
1579 // animation.
1580 Tab* currentTab = [_model currentTab];
1581 if (currentTab) {
1582 [self tabSelected:currentTab];
1583 }
sdefresnee65fd872016-12-19 13:38:131584 startVoiceSearchIfNecessaryBlock();
1585 });
1586 } else {
1587 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1588 // snapshot before adding the views needed for the background animation.
1589 Tab* topTab = [_model currentTab];
1590 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1591 visibleFrameOnly:self.isToolbarOnScreen];
1592 // Add three layers in order on top of the contentArea for the animation:
1593 // 1. The black "background" screen.
1594 base::scoped_nsobject<UIView> background(
1595 [[UIView alloc] initWithFrame:[_contentArea bounds]]);
1596 InstallBackgroundInView(background);
1597 [_contentArea addSubview:background];
1598
1599 // 2. A CardView displaying the data from the current tab.
1600 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1601 NSString* title = [topTab title];
1602 if (![title length])
1603 title = [topTab urlDisplayString];
1604 [topCard setTitle:title];
1605 [topCard setFavicon:[topTab favicon]];
1606 [topCard setImage:image];
1607
1608 // 3. A new, blank CardView to represent the new tab being added.
1609 // Launch the new background tab animation.
1610 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1611 topCard, [_contentArea frame], IsPortrait(), ^{
1612 [background removeFromSuperview];
1613 [topCard removeFromSuperview];
1614 _inNewTabAnimation = NO;
1615 // Resnapshot the top card if it has its own toolbar, as the toolbar
1616 // will be captured in the new tab animation, but isn't desired for
1617 // the stack view snapshots.
1618 id nativeController = [self nativeControllerForTab:topTab];
1619 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1620 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1621 startVoiceSearchIfNecessaryBlock();
1622 });
1623 }
1624}
1625
1626#pragma mark - UI Configuration and Layout
1627
1628- (void)updateWithTabModel:(TabModel*)model
1629 browserState:(ios::ChromeBrowserState*)browserState {
1630 DCHECK(model);
1631 DCHECK(browserState);
1632 DCHECK(!_model);
1633 DCHECK(!_browserState);
1634 _browserState = browserState;
1635 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
1636 _model.reset([model retain]);
1637 [_model addObserver:self];
1638
1639 if (!_isOffTheRecord) {
1640 [DefaultIOSWebViewFactory
1641 registerWebViewFactory:[ChromeWebViewFactory class]];
1642 }
1643 NSUInteger count = [_model count];
1644 for (NSUInteger index = 0; index < count; ++index)
1645 [self installDelegatesForTab:[_model tabAtIndex:index]];
1646
1647 [self registerForNotifications];
1648
gambardbdc07cc2017-02-03 16:43:111649 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
1650 _browserState->GetRequestContext(), web::WebThread::GetBlockingPool());
sdefresnee65fd872016-12-19 13:38:131651 _dominantColorCache.reset([[NSMutableDictionary alloc] init]);
1652
sdefresnedc432f42017-01-17 14:36:591653 // Register for bookmark changed notification (BookmarkModel may be null
1654 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131655 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591656 if (_bookmarkModel) {
1657 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1658 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1659 }
sdefresnee65fd872016-12-19 13:38:131660}
1661
1662- (void)ensureViewCreated {
1663 ignore_result([self view]);
1664}
1665
1666- (void)browserStateDestroyed {
1667 [self setActive:NO];
1668 // Reset the toolbar opacity in case it was changed for contextual search.
1669 [self updateToolbarControlsAlpha:1.0];
1670 [self updateToolbarBackgroundAlpha:1.0];
1671 [_contextualSearchController close];
1672 _contextualSearchController.reset();
1673 [_contextualSearchPanel removeFromSuperview];
1674 [_contextualSearchMask removeFromSuperview];
1675 [_paymentRequestManager close];
1676 _paymentRequestManager.reset();
sdefresnee65fd872016-12-19 13:38:131677 [_toolbarController browserStateDestroyed];
1678 [_model browserStateDestroyed];
michaeldobc2f42e2017-01-12 19:04:471679 [_preloadController browserStateDestroyed];
1680 _preloadController.reset();
sdefresnee65fd872016-12-19 13:38:131681 // The file remover needs the browser state, so needs to be destroyed now.
1682 _externalFileRemover.reset();
1683 _browserState = nullptr;
1684}
1685
1686- (void)installFakeStatusBar {
1687 if (IsIPadIdiom()) {
1688 CGFloat statusBarHeight = StatusBarHeight();
1689 CGRect statusBarFrame =
1690 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1691 base::scoped_nsobject<UIView> statusBarView(
1692 [[UIView alloc] initWithFrame:statusBarFrame]);
1693 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1694 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1695 [statusBarView layer].zPosition = 99;
1696 [[self view] addSubview:statusBarView];
1697 }
1698
1699 // Add a white bar on phone so that the status bar on the NTP is white.
1700 if (!IsIPadIdiom()) {
1701 CGFloat statusBarHeight = StatusBarHeight();
1702 CGRect statusBarFrame =
1703 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1704 base::scoped_nsobject<UIView> statusBarView(
1705 [[UIView alloc] initWithFrame:statusBarFrame]);
1706 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1707 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1708 [self.view insertSubview:statusBarView atIndex:0];
1709 }
1710}
1711
1712// Create the UI elements. May or may not have valid browser state & tab model.
1713- (void)buildToolbarAndTabStrip {
1714 DCHECK([self isViewLoaded]);
1715 DCHECK(!_toolbarModelDelegate);
1716
1717 // Create the preload controller before the toolbar controller.
1718 if (!_preloadController) {
1719 _preloadController.reset([_dependencyFactory newPreloadController]);
1720 [_preloadController setDelegate:self];
1721 }
1722
1723 // Create the toolbar model and controller.
1724 _toolbarModelDelegate.reset(new ToolbarModelDelegateIOS(_model));
1725 _toolbarModelIOS.reset([_dependencyFactory
1726 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
1727 _toolbarController.reset([_dependencyFactory
1728 newWebToolbarControllerWithDelegate:self
1729 urlLoader:self
1730 preloadProvider:_preloadController.get()]);
1731 [_toolbarController setTabCount:[_model count]];
1732 if (_voiceSearchController.get())
1733 _voiceSearchController->SetDelegate(_toolbarController);
1734
1735 // If needed, create the tabstrip.
1736 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131737 _tabStripController.reset(
1738 [_dependencyFactory newTabStripControllerWithTabModel:_model]);
1739 _tabStripController.get().fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131740 }
1741
1742 // Create infobar container.
1743 if (!_infoBarContainerDelegate) {
1744 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1745 _infoBarContainer.reset(
1746 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1747 }
1748}
1749
1750// Enable functionality that only makes sense if the views are loaded and
1751// both browser state and tab model are valid.
1752- (void)addUIFunctionalityForModelAndBrowserState {
1753 DCHECK(_browserState);
1754 DCHECK(_model);
1755 DCHECK([self isViewLoaded]);
1756
1757 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1758
1759 infobars::InfoBarManager* infoBarManager =
1760 [[_model currentTab] infoBarManager];
1761 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1762
1763 // Create contextual search views and controller.
1764 if ([TouchToSearchPermissionsMediator isTouchToSearchAvailableOnDevice] &&
1765 !_browserState->IsOffTheRecord()) {
1766 _contextualSearchMask =
1767 [[[ContextualSearchMaskView alloc] init] autorelease];
1768 [self.view insertSubview:_contextualSearchMask
1769 belowSubview:[_toolbarController view]];
1770 _contextualSearchPanel = [self createPanelView];
1771 [self.view insertSubview:_contextualSearchPanel
1772 aboveSubview:[_toolbarController view]];
1773 _contextualSearchController.reset([[ContextualSearchController alloc]
1774 initWithBrowserState:_browserState
1775 delegate:self]);
1776 [_contextualSearchController setPanel:_contextualSearchPanel];
1777 [_contextualSearchController setTab:[_model currentTab]];
1778 }
1779
1780 if (experimental_flags::IsPaymentRequestEnabled()) {
1781 _paymentRequestManager.reset([[PaymentRequestManager alloc]
1782 initWithBaseViewController:self
1783 browserState:_browserState]);
1784 [_paymentRequestManager setWebState:[_model currentTab].webState];
1785 }
1786}
1787
1788// Set the frame for the various views. View must be loaded.
1789- (void)setUpViewLayout {
1790 DCHECK([self isViewLoaded]);
1791
1792 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1793
1794 CGFloat minY = [self headerOffset];
1795
1796 // If needed, position the tabstrip.
1797 if (IsIPadIdiom()) {
1798 [self layoutTabStripForWidth:widthOfView];
1799 [[self view] addSubview:[_tabStripController view]];
1800 minY += CGRectGetHeight([[_tabStripController view] frame]);
1801 }
1802
1803 // Position the toolbar next, either at the top of the browser view or
1804 // directly under the tabstrip.
1805 CGRect toolbarFrame = [[_toolbarController view] frame];
1806 toolbarFrame.origin = CGPointMake(0, minY);
1807 toolbarFrame.size.width = widthOfView;
1808 [[_toolbarController view] setFrame:toolbarFrame];
1809
1810 // Place the infobar container above the content area.
1811 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1812 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1813
1814 // Place the toolbar controller above the infobar container.
1815 [[self view] insertSubview:[_toolbarController view]
1816 aboveSubview:infoBarContainerView];
1817 minY += CGRectGetHeight(toolbarFrame);
1818
1819 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1820 // content slightly.
1821 minY -= [ToolbarController toolbarDropShadowHeight];
1822
1823 // Adjust the content area to be under the toolbar, for fullscreen or below
1824 // the toolbar is not fullscreen.
1825 CGRect contentFrame = [_contentArea frame];
1826 CGFloat marginWithHeader = StatusBarHeight();
1827 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1828 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1829 contentFrame.origin.y = overlap;
1830 [_contentArea setFrame:contentFrame];
1831
1832 // Adjust the infobar container to be either at the bottom of the screen
1833 // (iPhone) or on the lower toolbar edge (iPad).
1834 CGRect infoBarFrame = contentFrame;
1835 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1836 infoBarFrame.size.height = 0;
1837 [infoBarContainerView setFrame:infoBarFrame];
1838
1839 // Attach the typing shield to the content area but have it hidden.
1840 [_typingShield setFrame:[_contentArea frame]];
1841 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1842 [_typingShield setHidden:YES];
1843 _typingShield.accessibilityIdentifier = @"Typing Shield";
1844 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1845}
1846
1847- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1848 UIView* tabStripView = [_tabStripController view];
1849 CGRect tabStripFrame = [tabStripView frame];
1850 tabStripFrame.origin = CGPointZero;
1851 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1852 // because the CGPointZero above will break reset the offset, but it's not
1853 // clear what removing that will do.
1854 tabStripFrame.origin.y = [self headerOffset];
1855 tabStripFrame.size.width = maxWidth;
1856 [tabStripView setFrame:tabStripFrame];
1857}
1858
1859- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1860 DCHECK(tab);
1861 // Ensure that self.view is loaded to avoid errors that can otherwise occur
1862 // when accessing |_contentArea| below.
1863 if (!_contentArea)
1864 [self ensureViewCreated];
1865
1866 DCHECK(_contentArea);
1867 if (!_inNewTabAnimation) {
1868 // Hide findbar. |updateToolbar| will restore the findbar later.
1869 [self hideFindBarWithAnimation:NO];
1870
1871 // Make new content visible, resizing it first as the orientation may
1872 // have changed from the last time it was displayed.
1873 [[tab view] setFrame:_contentArea.bounds];
1874 [_contentArea displayContentView:[tab view]];
1875 }
1876 [self updateToolbar];
1877
1878 if (newSelection)
1879 [_toolbarController selectedTabChanged];
1880
1881 // Notify the Tab that it was displayed.
1882 [tab wasShown];
1883}
1884
1885- (void)initializeBookmarkInteractionController {
1886 if (_bookmarkInteractionController)
1887 return;
1888 _bookmarkInteractionController.reset([[BookmarkInteractionController alloc]
1889 initWithBrowserState:_browserState
1890 loader:self
1891 parentController:self]);
1892}
1893
1894// Update the state of back and forward buttons, hiding the forward button if
1895// there is nowhere to go. Assumes the model's current tab is up to date.
1896- (void)updateToolbar {
1897 // If the BVC has been partially torn down for low memory, wait for the
1898 // view rebuild to handle toolbar updates.
1899 if (!(_toolbarModelIOS && _browserState))
1900 return;
1901
1902 Tab* tab = [_model currentTab];
1903 if (![tab navigationManager])
1904 return;
1905 [_toolbarController updateToolbarState];
1906 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1907
1908 if (tab.isPrerenderTab && !_toolbarModelIOS->IsLoading())
1909 [_toolbarController showPrerenderingAnimation];
1910
1911 // Also update the loading state for the tools menu (that is really an
1912 // extension of the toolbar on the iPhone).
1913 if (!IsIPadIdiom())
1914 [[_toolbarController toolsPopupController]
1915 setIsTabLoading:_toolbarModelIOS->IsLoading()];
1916
rohitrao005a6432017-03-16 20:52:421917 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
1918 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:131919 [self showFindBarWithAnimation:NO
1920 selectText:YES
1921 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:361922 }
sdefresnee65fd872016-12-19 13:38:131923
1924 // Hide the toolbar if displaying phone NTP.
1925 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:551926 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:131927 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:551928 if (item) {
1929 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:131930 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
1931 hideToolbar = isNTP && !_isOffTheRecord &&
1932 ![_toolbarController isOmniboxFirstResponder] &&
1933 ![_toolbarController showingOmniboxPopup];
1934 }
1935 [[_toolbarController view] setHidden:hideToolbar];
1936 }
1937}
1938
1939- (void)updateDialogPresenterActiveState {
1940 self.dialogPresenter.active = self.active && self.viewVisible;
1941}
1942
1943- (void)dismissPopups {
jif7fed8122017-02-08 13:15:251944 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:131945 [self hidePageInfoPopupForView:nil];
1946 [_toolbarController dismissTabHistoryPopup];
1947 [[_model currentTab].webController recordStateInHistory];
1948}
1949
1950#pragma mark - Tap handling
1951
1952- (void)setLastTapPoint:(id)sender {
1953 CGPoint center;
1954 UIView* parentView = nil;
1955 if ([sender isKindOfClass:[UIView class]]) {
1956 center = [sender center];
1957 parentView = [sender superview];
1958 }
1959 if ([sender isKindOfClass:[ToolsMenuViewItem class]]) {
1960 parentView = [[sender tableViewCell] superview];
1961 center = [[sender tableViewCell] center];
1962 }
1963
1964 if (parentView) {
1965 _lastTapPoint = [parentView convertPoint:center toView:self.view];
1966 _lastTapTime = CACurrentMediaTime();
1967 }
1968}
1969
1970- (CGPoint)lastTapPoint {
1971 if (CACurrentMediaTime() - _lastTapTime < 1) {
1972 return _lastTapPoint;
1973 }
1974 return CGPointZero;
1975}
1976
1977- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
1978 UIView* view = gestureRecognizer.view;
1979 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
1980 _lastTapPoint =
1981 [[view superview] convertPoint:viewCoordinate toView:self.view];
1982 _lastTapTime = CACurrentMediaTime();
1983}
1984
1985- (BOOL)addTabIfNoTabWithNormalBrowserState {
1986 if (![_model count]) {
1987 if (!_isOffTheRecord) {
1988 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1989 transition:ui::PAGE_TRANSITION_TYPED];
1990 return YES;
1991 }
1992 }
1993 return NO;
1994}
1995
1996#pragma mark - Tab creation and selection
1997
1998// Called when either a tab finishes loading or when a tab with finished content
1999// is added directly to the model via pre-rendering.
2000- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2001 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2002
2003 // Persist the session on a delay.
2004 [_model saveSessionImmediately:NO];
2005}
2006
2007- (Tab*)addSelectedTabWithURL:(const GURL&)url
2008 postData:(TemplateURLRef::PostContent*)postData
2009 transition:(ui::PageTransition)transition {
2010 return [self addSelectedTabWithURL:url
2011 postData:postData
2012 atIndex:[_model count]
2013 transition:transition];
2014}
2015
2016- (Tab*)addSelectedTabWithURL:(const GURL&)url
2017 transition:(ui::PageTransition)transition {
2018 return [self addSelectedTabWithURL:url
2019 atIndex:[_model count]
2020 transition:transition];
2021}
2022
2023- (Tab*)addSelectedTabWithURL:(const GURL&)url
2024 atIndex:(NSUInteger)position
2025 transition:(ui::PageTransition)transition {
2026 return [self addSelectedTabWithURL:url
2027 postData:NULL
2028 atIndex:position
2029 transition:transition];
2030}
2031
2032- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2033 postData:(TemplateURLRef::PostContent*)postData
2034 atIndex:(NSUInteger)position
2035 transition:(ui::PageTransition)transition {
2036 if (position == NSNotFound)
2037 position = [_model count];
2038 DCHECK(position <= [_model count]);
2039
2040 web::NavigationManager::WebLoadParams params(URL);
2041 params.transition_type = transition;
2042 if (postData) {
2043 // Extract the content type and post params from |postData| and add them
2044 // to the load params.
2045 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2046 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2047 length:postData->second.length()];
2048 params.post_data.reset([data retain]);
2049 params.extra_headers.reset([@{ @"Content-Type" : contentType } retain]);
2050 }
sdefresnea6395912017-03-01 01:14:352051 Tab* tab = [_model insertTabWithLoadParams:params
2052 opener:nil
2053 openedByDOM:NO
2054 atIndex:position
2055 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132056 return tab;
2057}
2058
olivierrobin889af53f2017-03-01 14:56:322059// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132060- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322061 web::WebState* webState = tab.webState;
2062 if (!webState)
2063 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392064 web::NavigationItem* visibleItem =
2065 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322066 if (!visibleItem)
2067 return NO;
2068 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132069}
2070
2071- (void)expectNewForegroundTab {
2072 _expectingForegroundTab = YES;
2073}
2074
2075- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2076 CGRect viewBounds, remainder;
2077 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2078 CGRectMinYEdge);
2079 return [[[UIImageView alloc] initWithFrame:viewBounds] autorelease];
2080}
2081
2082- (UIImageView*)pageOpenCloseAnimationView {
2083 CGRect frame = [_contentArea bounds];
2084
2085 frame.size.height = frame.size.height - [self headerHeight];
2086 frame.origin.y = [self headerHeight];
2087
2088 UIImageView* pageView =
2089 [[[UIImageView alloc] initWithFrame:frame] autorelease];
2090 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2091 pageView.center = center;
2092
2093 pageView.backgroundColor = [UIColor whiteColor];
2094 return pageView;
2095}
2096
2097- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142098 // Unregistration happens when the Tab is removed from the TabModel.
sdefresnee65fd872016-12-19 13:38:132099 tab.dialogDelegate = self;
2100 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132101 tab.passKitDialogProvider = self;
2102 tab.fullScreenControllerDelegate = self;
2103 if (!IsIPadIdiom()) {
2104 tab.overscrollActionsControllerDelegate = self;
2105 }
olivierrobin9ce77b82017-01-12 17:29:192106 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132107 tab.tabSnapshottingDelegate = self;
2108 // Install the proper CRWWebController delegates.
2109 tab.webController.nativeProvider = self;
2110 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512111 // BrowserViewController presents SKStoreKitViewController on behalf of a
2112 // tab.
2113 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2114 if (tabHelper)
2115 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132116 tab.webState->SetDelegate(_webStateDelegate.get());
2117}
2118
sdefresne49cf2862017-03-15 13:46:142119- (void)uninstallDelegatesForTab:(Tab*)tab {
2120 tab.dialogDelegate = nil;
2121 tab.snapshotOverlayProvider = nil;
2122 tab.passKitDialogProvider = nil;
2123 tab.fullScreenControllerDelegate = nil;
2124 if (!IsIPadIdiom()) {
2125 tab.overscrollActionsControllerDelegate = nil;
2126 }
2127 tab.tabHeadersDelegate = nil;
2128 tab.tabSnapshottingDelegate = nil;
2129 tab.webController.nativeProvider = nil;
2130 tab.webController.swipeRecognizerProvider = nil;
2131 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2132 if (tabHelper)
2133 tabHelper->SetLauncher(nil);
2134 tab.webState->SetDelegate(nullptr);
2135}
2136
sdefresnee65fd872016-12-19 13:38:132137// Called when a tab is selected in the model. Make any required view changes.
2138// The notification will not be sent when the tab is already the selected tab.
2139- (void)tabSelected:(Tab*)tab {
2140 DCHECK(tab);
2141
2142 // Ignore changes while the tab stack view is visible (or while suspended).
2143 // The display will be refreshed when this view becomes active again.
2144 if (!self.visible || ![_model webUsageEnabled])
2145 return;
2146
2147 [self displayTab:tab isNewSelection:YES];
2148
2149 if (_expectingForegroundTab && !_inNewTabAnimation) {
2150 // Now that the new tab has been displayed, return to normal. Rather than
2151 // keep a reference to the previous tab, just turn off preview mode for all
2152 // tabs (since doing so is a no-op for the tabs that don't have it set).
2153 _expectingForegroundTab = NO;
2154 for (Tab* tab in _model.get()) {
2155 [tab.webController setOverlayPreviewMode:NO];
2156 }
2157 }
2158}
2159
2160#pragma mark - External files
2161
2162- (NSSet*)referencedExternalFiles {
2163 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2164
2165 // TODO(noyau): this is incorrect, the caller should know that the model is
2166 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592167 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132168 return filesReferencedByTabs;
2169
2170 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2171 _bookmarkModel->GetBookmarks(&bookmarks);
2172 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2173 for (const auto& bookmark : bookmarks) {
2174 GURL bookmarkUrl = bookmark.url;
2175 if (UrlIsExternalFileReference(bookmarkUrl)) {
2176 [bookmarkedFiles
2177 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2178 }
2179 }
2180 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2181}
2182
2183- (void)removeExternalFilesImmediately:(BOOL)immediately
2184 completionHandler:(ProceduralBlock)completionHandler {
2185 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2186 DCHECK(!_isOffTheRecord);
2187 _externalFileRemover.reset(new ExternalFileRemover(self));
2188 // Delay the cleanup of the unreferenced files received from other apps
2189 // to not impact startup performance.
2190 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2191 _externalFileRemover->RemoveAfterDelay(
2192 base::TimeDelta::FromSeconds(delay),
2193 base::BindBlock(completionHandler ? completionHandler
2194 : ^{
2195 }));
2196}
2197
2198#pragma mark - SnapshotOverlayProvider methods
2199
2200- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2201 NSMutableArray* overlays = [NSMutableArray array];
2202 if (![_model webUsageEnabled]) {
2203 return overlays;
2204 }
2205 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2206 if (voiceSearchView) {
2207 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
2208 base::scoped_nsobject<SnapshotOverlay> voiceSearchOverlay(
2209 [[SnapshotOverlay alloc] initWithView:voiceSearchView
2210 yOffset:voiceSearchYOffset]);
2211 [overlays addObject:voiceSearchOverlay];
2212 }
2213 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2214 if (infoBarView) {
2215 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
2216 base::scoped_nsobject<SnapshotOverlay> infoBarOverlay(
2217 [[SnapshotOverlay alloc] initWithView:infoBarView
2218 yOffset:infoBarYOffset]);
2219 [overlays addObject:infoBarOverlay];
2220 }
2221 return overlays;
2222}
2223
2224#pragma mark -
2225
2226- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2227 if (IsIPadIdiom()) {
2228 // Not using overlays on iPad because the content is pushed down by
2229 // infobar and the transition between snapshot and fresh page can
2230 // cause both snapshot and real infobars to appear at the same time.
2231 return nil;
2232 }
2233 Tab* currentTab = [_model currentTab];
2234 if (tab && tab == currentTab) {
2235 infobars::InfoBarManager* infoBarManager = [currentTab infoBarManager];
2236 if (infoBarManager->infobar_count() > 0) {
2237 DCHECK(_infoBarContainer);
2238 return _infoBarContainer->view();
2239 }
2240 }
2241 return nil;
2242}
2243
2244- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
2245 if (tab != [_model currentTab] || !_infoBarContainer.get()) {
2246 // There is no UI representation for non-current tabs or there is
2247 // no _infoBarContainer instantiated yet.
2248 // Return offset outside of tab.
2249 return CGRectGetMaxY(self.view.frame);
2250 } else if (IsIPadIdiom()) {
2251 // The infobars on iPad are display at the top of a tab.
2252 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2253 } else {
2254 // The infobars on iPhone are displayed at the bottom of a tab.
2255 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2256 return CGRectGetMaxY(visibleFrame) -
2257 CGRectGetHeight(_infoBarContainer->view().frame);
2258 }
2259}
2260
2261- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2262 Tab* currentTab = [_model currentTab];
2263 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2264 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2265 return _voiceSearchBar;
2266 }
2267 return nil;
2268}
2269
2270- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2271 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2272 // There is no UI representation for non-current tabs or there is
2273 // no visible voice search. Return offset outside of tab.
2274 return CGRectGetMaxY(self.view.frame);
2275 } else {
2276 // The voice search bar on iPhone is displayed at the bottom of a tab.
2277 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2278 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2279 }
2280}
2281
2282- (void)ensureVoiceSearchControllerCreated {
2283 if (!_voiceSearchController.get()) {
2284 VoiceSearchProvider* provider =
2285 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2286 if (provider) {
2287 _voiceSearchController =
2288 provider->CreateVoiceSearchController(_browserState);
2289 _voiceSearchController->SetDelegate(_toolbarController);
2290 }
2291 }
2292}
2293
2294- (void)ensureVoiceSearchBarCreated {
2295 if (_voiceSearchBar)
2296 return;
2297
2298 CGFloat width = CGRectGetWidth([[self view] bounds]);
2299 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2300 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
kkhorimotoc2cdf6f42017-01-24 21:37:372301 _voiceSearchBar.reset([ios::GetChromeBrowserProvider()
2302 ->GetVoiceSearchProvider()
2303 ->BuildVoiceSearchBar(frame) retain]);
sdefresnee65fd872016-12-19 13:38:132304 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2305 [_voiceSearchBar setHidden:YES];
2306 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2307 UIViewAutoresizingFlexibleWidth];
2308 [self.view insertSubview:_voiceSearchBar
2309 belowSubview:_infoBarContainer->view()];
2310}
2311
2312- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2313 // Voice search bar exists and is shown/hidden.
2314 BOOL show = self.shouldShowVoiceSearchBar;
2315 if (_voiceSearchBar && _voiceSearchBar.get().hidden != show)
2316 return;
2317
2318 // Voice search bar doesn't exist and thus is not visible.
2319 if (!_voiceSearchBar && !show)
2320 return;
2321
2322 if (animated)
2323 [_voiceSearchBar.get() animateToBecomeVisible:show];
2324 else
2325 _voiceSearchBar.get().hidden = !show;
2326}
2327
2328- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2329 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2330 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2331 self.shouldShowVoiceSearchBar) {
2332 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2333 // animations.
2334 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar.get());
2335 }
2336 id currentNativeController =
2337 [self nativeControllerForTab:self.tabModel.currentTab];
2338 Protocol* possibleOwnerProtocol =
2339 @protocol(LogoAnimationControllerOwnerOwner);
2340 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2341 [currentNativeController logoAnimationControllerOwner]) {
2342 // If the current native controller is showing a GLIF view (e.g. the NTP
2343 // when there is no doodle), use that GLIFControllerOwner.
2344 return [currentNativeController logoAnimationControllerOwner];
2345 }
2346 return nil;
2347}
2348
2349#pragma mark - PassKitDialogProvider methods
2350
2351- (void)presentPassKitDialog:(NSData*)data {
2352 NSError* error = nil;
2353 base::scoped_nsobject<PKPass> pass;
2354 if (data)
2355 pass.reset([[PKPass alloc] initWithData:data error:&error]);
2356 if (error || !data) {
2357 if ([_model currentTab]) {
2358 infobars::InfoBarManager* infoBarManager =
2359 [[_model currentTab] infoBarManager];
2360 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2361 // NULL, replace if with DCHECK).
2362 if (infoBarManager)
2363 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2364 }
2365 } else {
2366 PKAddPassesViewController* passKitViewController =
2367 [_dependencyFactory newPassKitViewControllerForPass:pass];
2368 if (passKitViewController) {
2369 [self presentViewController:passKitViewController
2370 animated:YES
2371 completion:^{
2372 }];
2373 }
2374 }
2375}
2376
2377- (UIStatusBarStyle)preferredStatusBarStyle {
2378 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2379 : UIStatusBarStyleDefault;
2380}
2381
2382#pragma mark - CRWWebStateDelegate methods.
2383
eugenebut75a06fa72017-01-09 17:09:552384- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512385 createNewWebStateForURL:(const GURL&)URL
2386 openerURL:(const GURL&)openerURL
2387 initiatedByUser:(BOOL)initiatedByUser {
2388 // Check if requested web state is a popup and block it if necessary.
2389 if (!initiatedByUser) {
2390 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2391 if (helper->ShouldBlockPopup(openerURL)) {
2392 web::NavigationItem* item =
2393 webState->GetNavigationManager()->GetLastCommittedItem();
2394 web::Referrer referrer(openerURL, item->GetReferrer().policy);
2395 helper->HandlePopup(URL, referrer);
2396 return nil;
2397 }
2398 }
2399
2400 // Requested web state should not be blocked from opening.
2401 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2402 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2403
2404 // Tabs open by DOM are always renderer initiated.
2405 web::NavigationManager::WebLoadParams params(GURL{});
2406 params.transition_type = ui::PAGE_TRANSITION_LINK;
2407 params.is_renderer_initiated = true;
2408 Tab* childTab = [[self tabModel]
2409 insertTabWithLoadParams:params
2410 opener:currentTab
2411 openedByDOM:YES
2412 atIndex:TabModelConstants::kTabPositionAutomatically
2413 inBackground:NO];
2414 return childTab.webState;
2415}
2416
eugenebutb46b2122017-03-14 02:43:262417- (void)closeWebState:(web::WebState*)webState {
2418 // Only allow a web page to close itself if it was opened by DOM, or if there
2419 // are no navigation items.
2420 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2421 DCHECK([[tab navigationManagerImpl]->GetSessionController() isOpenedByDOM] ||
2422 ![tab navigationManager]->GetItemCount());
2423
2424 if (![self tabModel])
2425 return;
2426
2427 NSUInteger index = [[self tabModel] indexOfTab:tab];
2428 if (index != NSNotFound)
2429 [[self tabModel] closeTabAtIndex:index];
2430}
2431
eugenebut275f5892017-03-09 22:20:512432- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552433 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2434 switch (params.disposition) {
2435 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2436 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2437 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352438 insertTabWithURL:params.url
2439 referrer:params.referrer
2440 transition:params.transition
2441 opener:LegacyTabHelper::GetTabForWebState(webState)
2442 openedByDOM:NO
2443 atIndex:TabModelConstants::kTabPositionAutomatically
2444 inBackground:(params.disposition ==
2445 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552446 return tab.webState;
2447 }
2448 case WindowOpenDisposition::CURRENT_TAB: {
2449 web::NavigationManager::WebLoadParams loadParams(params.url);
2450 loadParams.referrer = params.referrer;
2451 loadParams.transition_type = params.transition;
2452 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2453 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2454 return webState;
2455 }
eugenebutd0984e82017-02-22 23:47:512456 case WindowOpenDisposition::NEW_POPUP: {
2457 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352458 insertTabWithURL:params.url
2459 referrer:params.referrer
2460 transition:params.transition
2461 opener:LegacyTabHelper::GetTabForWebState(webState)
2462 openedByDOM:YES
2463 atIndex:TabModelConstants::kTabPositionAutomatically
2464 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512465 return tab.webState;
2466 }
eugenebut75a06fa72017-01-09 17:09:552467 default:
2468 NOTIMPLEMENTED();
2469 return nullptr;
2470 };
2471}
2472
sdefresnee65fd872016-12-19 13:38:132473- (BOOL)webState:(web::WebState*)webState
2474 handleContextMenu:(const web::ContextMenuParams&)params {
2475 // Prevent context menu from displaying for a tab which is no longer the
2476 // current one.
2477 if (webState != [_model currentTab].webState) {
2478 return NO;
2479 }
2480
2481 // No custom context menu if no valid url is available in |params|.
2482 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
2483 return NO;
2484 }
2485
2486 DCHECK(_browserState);
2487 DCHECK([_model currentTab]);
2488
2489 _contextMenuCoordinator.reset([[ContextMenuCoordinator alloc]
2490 initWithBaseViewController:self
2491 params:params]);
2492
2493 NSString* title = nil;
2494 ProceduralBlock action = nil;
2495
2496 base::WeakNSObject<BrowserViewController> weakSelf(self);
2497 GURL link = params.link_url;
2498 bool isLink = link.is_valid();
2499 GURL imageUrl = params.src_url;
2500 bool isImage = imageUrl.is_valid();
2501
2502 if (isLink) {
2503 if (link.SchemeIs(url::kJavaScriptScheme)) {
2504 // Open
2505 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2506 action = ^{
2507 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2508 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2509 };
2510 [_contextMenuCoordinator addItemWithTitle:title action:action];
2511 }
2512
2513 if (web::UrlHasWebScheme(link)) {
2514 web::Referrer referrer([_model currentTab].url, params.referrer_policy);
2515
sdefresnee65fd872016-12-19 13:38:132516 // Open in New Tab.
2517 title = l10n_util::GetNSStringWithFixup(
2518 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2519 action = ^{
2520 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
2521 [weakSelf webPageOrderedOpen:link
2522 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132523 inBackground:YES
2524 appendTo:kCurrentTab];
2525 };
2526 [_contextMenuCoordinator addItemWithTitle:title action:action];
2527 if (!_isOffTheRecord) {
2528 // Open in Incognito Tab.
2529 title = l10n_util::GetNSStringWithFixup(
2530 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2531 action = ^{
2532 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2533 [weakSelf webPageOrderedOpen:link
2534 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132535 inIncognito:YES
2536 inBackground:NO
2537 appendTo:kCurrentTab];
2538 };
2539 [_contextMenuCoordinator addItemWithTitle:title action:action];
2540 }
olivierrobin51d4cf42017-01-17 13:32:352541 }
2542 if (link.SchemeIsHTTPOrHTTPS() &&
2543 reading_list::switches::IsReadingListEnabled()) {
2544 NSString* innerText = params.link_text;
2545 if ([innerText length] > 0) {
2546 // Add to reading list.
2547 title = l10n_util::GetNSStringWithFixup(
2548 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2549 action = ^{
2550 Record(ACTION_READ_LATER, isImage, isLink);
2551 [weakSelf addToReadingListURL:link title:innerText];
2552 };
2553 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532554 }
sdefresnee65fd872016-12-19 13:38:132555 }
2556 // Copy Link.
2557 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2558 action = ^{
2559 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282560 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132561 };
2562 [_contextMenuCoordinator addItemWithTitle:title action:action];
2563 }
2564 if (isImage) {
2565 web::Referrer referrer([_model currentTab].url, params.referrer_policy);
2566 // Save Image.
2567 if (experimental_flags::IsDownloadRenamingEnabled()) {
2568 title = l10n_util::GetNSStringWithFixup(
2569 IDS_IOS_CONTENT_CONTEXT_DOWNLOADIMAGE);
2570 } else {
2571 title =
2572 l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
2573 }
2574 action = ^{
2575 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2576 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2577 };
2578 [_contextMenuCoordinator addItemWithTitle:title action:action];
2579 // Open Image.
2580 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2581 action = ^{
2582 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2583 [weakSelf loadURL:imageUrl
2584 referrer:referrer
2585 transition:ui::PAGE_TRANSITION_LINK
2586 rendererInitiated:YES];
2587 };
2588 [_contextMenuCoordinator addItemWithTitle:title action:action];
2589 // Open Image In New Tab.
2590 title = l10n_util::GetNSStringWithFixup(
2591 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2592 action = ^{
2593 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2594 [weakSelf webPageOrderedOpen:imageUrl
2595 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132596 inBackground:true
2597 appendTo:kCurrentTab];
2598 };
2599 [_contextMenuCoordinator addItemWithTitle:title action:action];
2600
2601 TemplateURLService* service =
2602 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
2603 TemplateURL* defaultURL = service->GetDefaultSearchProvider();
2604 if (defaultURL && !defaultURL->image_url().empty() &&
2605 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2606 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2607 defaultURL->short_name());
2608 action = ^{
2609 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2610 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2611 };
2612 [_contextMenuCoordinator addItemWithTitle:title action:action];
2613 }
2614 }
2615
2616 [_contextMenuCoordinator start];
2617 return YES;
2618}
2619
eugenebutb739bdc2017-01-25 06:32:482620- (void)webState:(web::WebState*)webState
2621 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2622 // Display the action sheet with the arrow pointing at the top center of the
2623 // web contents.
sdefresne0452a9d2017-02-09 15:33:282624 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482625 UIView* view = webState->GetView();
2626 CGPoint dialogLocation =
2627 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282628 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032629 auto* helper = RepostFormTabHelper::FromWebState(webState);
eugenebutcae3d9e62017-01-27 20:01:052630 helper->PresentDialog(dialogLocation, base::BindBlock(^(bool shouldContinue) {
2631 handler(shouldContinue);
2632 }));
eugenebutb739bdc2017-01-25 06:32:482633}
2634
sdefresnee65fd872016-12-19 13:38:132635- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2636 (web::WebState*)webState {
2637 return _javaScriptDialogPresenter.get();
2638}
2639
eugenebut63232102017-01-19 16:19:402640- (void)webState:(web::WebState*)webState
2641 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2642 proposedCredential:(NSURLCredential*)proposedCredential
2643 completionHandler:(void (^)(NSString* username,
2644 NSString* password))handler {
2645 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2646 proposedCredential:proposedCredential
2647 webState:webState
2648 completionHandler:handler];
2649}
2650
sdefresnee65fd872016-12-19 13:38:132651#pragma mark - FullScreenControllerDelegate methods
2652
2653- (CGFloat)headerOffset {
2654 if (IsIPadIdiom())
2655 return StatusBarHeight();
2656 return 0.0;
2657}
2658
2659- (const std::vector<HeaderDefinition>)headerViews {
2660 std::vector<HeaderDefinition> results;
2661 if (![self isViewLoaded])
2662 return results;
2663
2664 if (!IsIPadIdiom()) {
2665 if ([_toolbarController view]) {
2666 HeaderDefinition header = {
2667 base::scoped_nsobject<UIView>([[_toolbarController view] retain]),
2668 Hideable, [ToolbarController toolbarDropShadowHeight], 0.0,
2669 };
2670 results.push_back(header);
2671 }
2672 } else {
2673 if ([_tabStripController view]) {
2674 HeaderDefinition header = {
2675 base::scoped_nsobject<UIView>([[_tabStripController view] retain]),
2676 Hideable, 0.0, 0.0,
2677 };
2678 results.push_back(header);
2679 }
2680 if ([_toolbarController view]) {
2681 HeaderDefinition header = {
2682 base::scoped_nsobject<UIView>([[_toolbarController view] retain]),
2683 Hideable, [ToolbarController toolbarDropShadowHeight], 0.0,
2684 };
2685 results.push_back(header);
2686 }
2687 if ([_findBarController view]) {
2688 HeaderDefinition header = {
2689 base::scoped_nsobject<UIView>([[_findBarController view] retain]),
2690 Overlap, 0.0, kIPadFindBarOverlap,
2691 };
2692 results.push_back(header);
2693 }
2694 }
2695 return results;
2696}
2697
2698- (UIView*)footerView {
2699 return _voiceSearchBar;
2700}
2701
2702- (CGFloat)headerHeight {
2703 return [self headerHeightForTab:[_model currentTab]];
2704}
2705
2706- (CGFloat)headerHeightForTab:(Tab*)tab {
2707 id nativeController = [self nativeControllerForTab:tab];
2708 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2709 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2710 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2711 // On iPhone, don't add any header height for ToolbarOwner native
2712 // controllers when they're displaying their own toolbar.
2713 return 0;
2714 }
2715
2716 const std::vector<HeaderDefinition> views = [self headerViews];
2717
2718 CGFloat height = [self headerOffset];
2719 for (const auto& header : views) {
2720 if (header.view && header.behaviour == Hideable) {
2721 height += CGRectGetHeight([header.view frame]) -
2722 header.heightAdjustement - header.inset;
2723 }
2724 }
2725
2726 return height - StatusBarHeight();
2727}
2728
2729- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:192730 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:132731}
2732
2733- (CGFloat)currentHeaderOffset {
2734 const std::vector<HeaderDefinition> headers = [self headerViews];
2735 if (!headers.size())
2736 return 0.0;
2737
2738 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
2739 // API documentation.
2740 if ([[[self tabModel] currentTab] isPrerenderTab])
2741 return [self headerHeight];
2742
2743 UIView* topHeader = headers[0].view;
2744 return -(topHeader.frame.origin.y - [self headerOffset]);
2745}
2746
2747- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
2748 UIView* footer = [self footerView];
2749 CGFloat headerHeight = [self headerHeight];
2750 if (!footer || headerHeight == 0)
2751 return 0.0;
2752
2753 CGFloat footerHeight = CGRectGetHeight(footer.frame);
2754 CGFloat offset = headerOffset * footerHeight / headerHeight;
2755 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
2756}
2757
2758- (void)fullScreenController:(FullScreenController*)controller
2759 headerAnimationCompleted:(BOOL)completed
2760 offset:(CGFloat)offset {
2761 if (completed)
justincohen04c27772016-12-21 20:16:592762 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:132763}
2764
2765- (void)setFramesForHeaders:(const std::vector<HeaderDefinition>)headers
2766 atOffset:(CGFloat)headerOffset {
2767 CGFloat height = [self headerOffset];
2768 for (const auto& header : headers) {
2769 CGRect frame = [header.view frame];
2770 frame.origin.y = height - headerOffset - header.inset;
2771 [header.view setFrame:frame];
2772 if (header.behaviour != Overlap)
2773 height += CGRectGetHeight(frame);
2774 }
2775}
2776
2777- (void)fullScreenController:(FullScreenController*)fullScreenController
2778 drawHeaderViewFromOffset:(CGFloat)headerOffset
2779 animate:(BOOL)animate {
2780 if ([_sideSwipeController inSwipe])
2781 return;
2782
2783 CGRect footerFrame = CGRectZero;
2784 UIView* footer = nil;
2785 // Only animate the voice search bar if the tab is a voice search results tab.
2786 if ([_model currentTab].isVoiceSearchResultsTab) {
2787 footer = [self footerView];
2788 footerFrame = footer.frame;
2789 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2790 }
2791
2792 const std::vector<HeaderDefinition> headers = [self headerViews];
2793 void (^block)(void) = ^{
2794 [self setFramesForHeaders:headers atOffset:headerOffset];
2795 footer.frame = footerFrame;
2796 };
2797 void (^completion)(BOOL) = ^(BOOL finished) {
2798 [self fullScreenController:fullScreenController
2799 headerAnimationCompleted:finished
2800 offset:headerOffset];
2801 };
2802 if (animate) {
2803 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2804 delay:0.0
2805 options:UIViewAnimationOptionBeginFromCurrentState
2806 animations:block
2807 completion:completion];
2808 } else {
2809 block();
2810 completion(YES);
2811 }
2812}
2813
2814- (void)fullScreenController:(FullScreenController*)fullScreenController
2815 drawHeaderViewFromOffset:(CGFloat)headerOffset
2816 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
2817 changeTopContentPadding:(BOOL)changeTopContentPadding
2818 scrollingToOffset:(CGFloat)contentOffset {
2819 DCHECK(webViewProxy);
2820 if ([_sideSwipeController inSwipe])
2821 return;
2822
2823 CGRect footerFrame;
2824 UIView* footer = nil;
2825 // Only animate the voice search bar if the tab is a voice search results tab.
2826 if ([_model currentTab].isVoiceSearchResultsTab) {
2827 footer = [self footerView];
2828 footerFrame = footer.frame;
2829 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2830 }
2831
2832 const std::vector<HeaderDefinition> headers = [self headerViews];
2833 void (^block)(void) = ^{
2834 [self setFramesForHeaders:headers atOffset:headerOffset];
2835 footer.frame = footerFrame;
2836 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
2837 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
2838 if (changeTopContentPadding)
2839 webViewProxy.topContentPadding = contentOffset;
2840 };
2841 void (^completion)(BOOL) = ^(BOOL finished) {
2842 [self fullScreenController:fullScreenController
2843 headerAnimationCompleted:finished
2844 offset:headerOffset];
2845 };
2846
2847 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2848 delay:0.0
2849 options:UIViewAnimationOptionBeginFromCurrentState
2850 animations:block
2851 completion:completion];
2852}
2853
2854#pragma mark - VoiceSearchBarOwner
2855
2856- (id<VoiceSearchBar>)voiceSearchBar {
2857 return _voiceSearchBar;
2858}
2859
2860#pragma mark - Install OverScrollActionController method.
2861- (void)setOverScrollActionControllerToStaticNativeContent:
2862 (StaticHtmlNativeContent*)nativeContent {
2863 if (!IsIPadIdiom() && !FirstRun::IsChromeFirstRun()) {
2864 OverscrollActionsController* controller =
2865 [[[OverscrollActionsController alloc]
2866 initWithScrollView:[nativeContent scrollView]] autorelease];
sdefresnee65fd872016-12-19 13:38:132867 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:052868 OverscrollStyle style = _isOffTheRecord
2869 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2870 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:132871 controller.style = style;
2872 nativeContent.overscrollActionsController = controller;
2873 }
2874}
2875
2876#pragma mark - OverscrollActionsControllerDelegate methods.
2877
2878- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:052879 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:132880 switch (action) {
rohitrao922b7111c2017-01-03 14:31:052881 case OverscrollAction::NEW_TAB:
sdefresnee65fd872016-12-19 13:38:132882 [self newTab:nil];
2883 break;
rohitrao922b7111c2017-01-03 14:31:052884 case OverscrollAction::CLOSE_TAB:
sdefresnee65fd872016-12-19 13:38:132885 [self closeCurrentTab];
2886 break;
liaoyuke563dc4a2017-03-17 18:36:292887 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:132888 if ([[[_model currentTab] webController] loadPhase] ==
2889 web::PAGE_LOADING) {
2890 [[_model currentTab] stopLoading];
2891 }
liaoyuke563dc4a2017-03-17 18:36:292892
2893 web::WebState* webState = [_model currentTab].webState;
2894 if (webState)
2895 // |check_for_repost| is true because the reload is explicitly initiated
2896 // by the user.
2897 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
2898 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:132899 break;
liaoyuke563dc4a2017-03-17 18:36:292900 }
rohitrao922b7111c2017-01-03 14:31:052901 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:132902 NOTREACHED();
2903 break;
2904 }
2905}
2906
2907- (BOOL)shouldAllowOverscrollActions {
2908 return YES;
2909}
2910
2911- (UIView*)headerView {
2912 return [_toolbarController view];
2913}
2914
2915- (UIView*)toolbarSnapshotView {
2916 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
2917}
2918
2919- (CGFloat)overscrollActionsControllerHeaderInset:
2920 (OverscrollActionsController*)controller {
2921 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
2922 return [self headerHeight];
2923 else
2924 return 0;
2925}
2926
2927- (CGFloat)overscrollHeaderHeight {
2928 return [self headerHeight] + StatusBarHeight();
2929}
2930
2931#pragma mark - TabSnapshottingDelegate methods.
2932
2933- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
2934 CGRect pageContentArea = _contentArea.bounds;
2935 if ([_model webUsageEnabled])
2936 pageContentArea = tab.view.bounds;
2937 CGFloat headerHeight = [self headerHeightForTab:tab];
2938 id nativeController = [self nativeControllerForTab:tab];
2939 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
2940 headerHeight += [nativeController toolbarHeight];
2941 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
2942 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
2943}
2944
2945#pragma mark - NewTabPageObserver methods.
2946
2947- (void)selectedPanelDidChange {
2948 [self updateToolbar];
2949}
2950
2951#pragma mark - CRWNativeContentProvider methods
2952
2953- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
2954 withError:(NSError*)error
2955 isPost:(BOOL)isPost {
2956 ErrorPageContent* errorPageContent =
2957 [[[ErrorPageContent alloc] initWithLoader:self
2958 browserState:self.browserState
2959 url:url
2960 error:error
2961 isPost:isPost
2962 isIncognito:_isOffTheRecord] autorelease];
2963 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
2964 return errorPageContent;
2965}
2966
2967- (BOOL)hasControllerForURL:(const GURL&)url {
2968 std::string host(url.host());
2969
2970 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost ||
2971 host == kChromeUITermsHost || host == kChromeUIOfflineHost;
2972}
2973
olivierrobind43eecb2017-01-27 20:35:262974- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
2975 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:132976 DCHECK(url.SchemeIs(kChromeUIScheme));
2977
2978 id<CRWNativeContent> nativeController = nil;
2979 std::string url_host = url.host();
2980 if (url_host == kChromeUINewTabHost || url_host == kChromeUIBookmarksHost) {
2981 NewTabPageController* pageController =
2982 [[[NewTabPageController alloc] initWithUrl:url
2983 loader:self
2984 focuser:_toolbarController
2985 ntpObserver:self
2986 browserState:_browserState
2987 colorCache:_dominantColorCache
2988 webToolbarDelegate:self
2989 tabModel:_model] autorelease];
2990 pageController.swipeRecognizerProvider = self.sideSwipeController;
2991
2992 // Panel is always NTP for iPhone.
2993 NewTabPage::PanelIdentifier panelType = NewTabPage::kMostVisitedPanel;
2994
2995 if (IsIPadIdiom()) {
2996 // New Tab Page can have multiple panels. Each panel is addressable
2997 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
2998 // the Most Visited page, chrome://newtab/#bookmarks takes user to
2999 // the Bookmark Manager, etc.
3000 // The utility functions NewTabPage::IdentifierFromFragment() and
3001 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3002 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3003 // without changing the URL since the URL may be chrome://bookmarks/#123.
3004 // If the URL is chrome://newtab/, pre-select the panel based on the
3005 // #fragment.
3006 panelType = url_host == kChromeUIBookmarksHost
3007 ? NewTabPage::kBookmarksPanel
3008 : NewTabPage::IdentifierFromFragment(url.ref());
3009 }
3010 [pageController selectPanel:panelType];
3011 nativeController = pageController;
3012 } else if (url_host == kChromeUITermsHost) {
3013 const std::string& filename = GetTermsOfServicePath();
3014
3015 StaticHtmlNativeContent* staticNativeController =
3016 [[[StaticHtmlNativeContent alloc]
3017 initWithResourcePathResource:base::SysUTF8ToNSString(filename)
3018 loader:self
3019 browserState:_browserState
3020 url:GURL(kChromeUITermsURL)] autorelease];
3021 [self setOverScrollActionControllerToStaticNativeContent:
3022 staticNativeController];
3023 nativeController = staticNativeController;
3024 } else if (url_host == kChromeUIOfflineHost) {
3025 StaticHtmlNativeContent* staticNativeController =
3026 [[[OfflinePageNativeContent alloc] initWithLoader:self
3027 browserState:_browserState
olivierrobind43eecb2017-01-27 20:35:263028 webState:webState
sdefresnee65fd872016-12-19 13:38:133029 URL:url] autorelease];
3030 [self setOverScrollActionControllerToStaticNativeContent:
3031 staticNativeController];
3032 nativeController = staticNativeController;
3033 } else if (url_host == kChromeUIExternalFileHost) {
3034 // Return an instance of the |ExternalFileController| only if the file is
3035 // still in the sandbox.
3036 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3037 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
3038 nativeController = [[[ExternalFileController alloc]
3039 initWithURL:url
3040 browserState:_browserState] autorelease];
3041 }
3042 } else {
3043 DCHECK(![self hasControllerForURL:url]);
3044 // In any other case the PageNotAvailableController is returned.
3045 nativeController =
3046 [[[PageNotAvailableController alloc] initWithUrl:url] autorelease];
3047 if (url_host == kChromeUIHistoryFrameHost) {
3048 base::mac::ObjCCastStrict<PageNotAvailableController>(nativeController)
3049 .descriptionText = l10n_util::GetNSStringFWithFixup(
3050 IDS_IOS_HISTORY_URL_NOT_AVAILABLE,
3051 base::UTF8ToUTF16(kChromeUIHistoryURL),
3052 l10n_util::GetStringUTF16(IDS_HISTORY_SHOW_HISTORY));
3053 }
3054 }
3055 // If a native controller is vended before its tab is added to the tab model,
3056 // use the temporary key and add it under the new tab's tabId in the
3057 // TabModelObserver callback. This happens:
3058 // - when there is no current tab (occurs when vending the NTP controller for
3059 // the first tab that is opened),
3060 // - when the current tab's url doesn't match |url| (occurs when a native
3061 // controller is opened in a new tab)
3062 // - when the current tab's url matches |url| and there is already a native
3063 // controller of the appropriate type vended to it (occurs when a native
3064 // controller is opened in a new tab from a tab with a matching URL, e.g.
3065 // opening an NTP when an NTP is already displayed in the current tab).
3066 // For normal page loads, history navigations, tab restorations, and crash
3067 // recoveries, the tab will already exist in the tab model and the tabId can
3068 // be used as the native controller key.
3069 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3070 // that native controllers vended here always correspond to the current tab.
3071 Tab* currentTab = [_model currentTab];
3072 NSString* nativeControllerKey = currentTab.tabId;
3073 if (!currentTab || currentTab.url != url ||
3074 [[_nativeControllersForTabIDs objectForKey:nativeControllerKey]
3075 isKindOfClass:[nativeController class]]) {
3076 nativeControllerKey = kNativeControllerTemporaryKey;
3077 }
3078 DCHECK(nativeControllerKey);
3079 [_nativeControllersForTabIDs setObject:nativeController
3080 forKey:nativeControllerKey];
3081 return nativeController;
3082}
3083
3084- (id)nativeControllerForTab:(Tab*)tab {
3085 id nativeController = [_nativeControllersForTabIDs objectForKey:tab.tabId];
3086 if (!nativeController) {
3087 // If there is no controller, check for a native controller stored under
3088 // the temporary key.
3089 nativeController = [_nativeControllersForTabIDs
3090 objectForKey:kNativeControllerTemporaryKey];
3091 }
3092 return nativeController;
3093}
3094
3095#pragma mark - DialogPresenterDelegate methods
3096
3097- (void)dialogPresenter:(DialogPresenter*)presenter
3098 willShowDialogForWebState:(web::WebState*)webState {
3099 for (Tab* iteratedTab in self.tabModel) {
3100 if ([iteratedTab webState] == webState) {
3101 self.tabModel.currentTab = iteratedTab;
3102 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3103 break;
3104 }
3105 }
3106}
3107
3108#pragma mark - Context menu methods
3109
3110- (void)searchByImageAtURL:(const GURL&)url
3111 referrer:(const web::Referrer)referrer {
3112 DCHECK(url.is_valid());
3113 base::WeakNSObject<BrowserViewController> weakSelf(self);
gambardbdc07cc2017-02-03 16:43:113114 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173115 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3116 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113117 DCHECK(data);
3118 dispatch_async(dispatch_get_main_queue(), ^{
3119 [weakSelf searchByImageData:data atURL:image_source_url];
3120 });
3121 };
3122 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133123 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3124 web::PolicyForNavigation(url, referrer));
3125}
3126
3127- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3128 NSData* imageData = data;
3129 UIImage* image = [UIImage imageWithData:imageData];
3130 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3131 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3132 // kSearchByImageMaxImageHeight).
3133 if (image &&
3134 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3135 (image.size.width > kSearchByImageMaxImageWidth ||
3136 image.size.height > kSearchByImageMaxImageHeight)) {
3137 CGSize newImageSize =
3138 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3139 image = [image gtm_imageByResizingToSize:newImageSize
3140 preserveAspectRatio:YES
3141 trimToFit:NO];
3142 imageData = UIImageJPEGRepresentation(image, 1.0);
3143 }
3144
3145 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3146 std::string byteString(bytes, [imageData length]);
3147
3148 TemplateURLService* templateUrlService =
3149 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
3150 TemplateURL* defaultURL = templateUrlService->GetDefaultSearchProvider();
3151 DCHECK(!defaultURL->image_url().empty());
3152 DCHECK(defaultURL->image_url_ref().IsValid(
3153 templateUrlService->search_terms_data()));
3154 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3155 search_args.image_url = imageURL;
3156 search_args.image_thumbnail_content = byteString;
3157
3158 // Generate the URL and populate |post_content| with the content type and
3159 // HTTP body for the request.
3160 TemplateURLRef::PostContent post_content;
3161 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3162 search_args, templateUrlService->search_terms_data(), &post_content));
3163 [self addSelectedTabWithURL:result
3164 postData:&post_content
3165 transition:ui::PAGE_TRANSITION_TYPED];
3166}
3167
3168- (void)saveImageAtURL:(const GURL&)url
3169 referrer:(const web::Referrer&)referrer {
3170 DCHECK(url.is_valid());
3171
gambard9efce7a2017-02-09 18:53:173172 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3173 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113174 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133175
gambard9efce7a2017-02-09 18:53:173176 base::FilePath::StringType extension;
3177
3178 bool extensionSuccess =
3179 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3180 if (!extensionSuccess || extension.length() == 0) {
3181 extension = "png";
3182 }
3183
3184 NSString* fileExtension =
3185 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3186 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113187 };
3188 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133189 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3190 web::PolicyForNavigation(url, referrer));
3191}
3192
gambard9efce7a2017-02-09 18:53:173193- (void)managePermissionAndSaveImage:(NSData*)data
3194 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133195 switch ([PHPhotoLibrary authorizationStatus]) {
3196 // User was never asked for permission to access photos.
3197 case PHAuthorizationStatusNotDetermined:
3198 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3199 // Call -saveImage again to check if chrome needs to display an error or
3200 // saves the image.
3201 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173202 [self managePermissionAndSaveImage:data
3203 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133204 }];
3205 break;
3206
3207 // The application doesn't have permission to access photo and the user
3208 // cannot grant it.
3209 case PHAuthorizationStatusRestricted:
3210 [self displayPrivacyErrorAlertOnMainQueue:
3211 l10n_util::GetNSString(
3212 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3213 break;
3214
3215 // The application doesn't have permission to access photo and the user
3216 // can grant it.
3217 case PHAuthorizationStatusDenied:
3218 [self displayImageErrorAlertWithSettingsOnMainQueue];
3219 break;
3220
3221 // The application has permission to access the photos.
3222 default: {
gambard9efce7a2017-02-09 18:53:173223 web::WebThread::PostTask(
3224 web::WebThread::FILE, FROM_HERE, base::BindBlock(^{
3225 [self saveImage:data withFileExtension:fileExtension];
3226 }));
sdefresnee65fd872016-12-19 13:38:133227 break;
3228 }
3229 }
3230}
3231
gambard9efce7a2017-02-09 18:53:173232- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension {
gambard16cfe1f2016-12-21 13:12:093233 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
gambard9efce7a2017-02-09 18:53:173234 stringByAppendingString:fileExtension];
sdefresnee65fd872016-12-19 13:38:133235 NSURL* fileURL =
3236 [NSURL fileURLWithPath:[NSTemporaryDirectory()
3237 stringByAppendingPathComponent:fileName]];
3238 NSError* error = nil;
3239 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3240
3241 // Error while writing the image to disk.
3242 if (error) {
3243 NSString* errorMessage = [NSString
3244 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3245 [error domain], [error code]];
3246 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3247 return;
3248 }
3249
3250 // Save the image to photos.
3251 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3252 [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
3253 }
3254 completionHandler:^(BOOL success, NSError* error) {
3255 // Callback for the image saving.
3256 [self finishSavingImageWithError:error];
3257
3258 // Cleanup the temporary file.
3259 web::WebThread::PostTask(
3260 web::WebThread::FILE, FROM_HERE, base::BindBlock(^{
3261 NSError* error = nil;
3262 [[NSFileManager defaultManager] removeItemAtURL:fileURL
3263 error:&error];
3264 }));
3265 }];
3266}
3267
3268- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3269 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3270 BOOL canGoToSetting =
3271 [[UIApplication sharedApplication] canOpenURL:settingURL];
3272 if (canGoToSetting) {
3273 dispatch_async(dispatch_get_main_queue(), ^{
3274 [self displayImageErrorAlertWithSettings:settingURL];
3275 });
3276 } else {
3277 [self displayPrivacyErrorAlertOnMainQueue:
3278 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3279 }
3280}
3281
3282- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3283 // Dismiss current alert.
3284 [_alertCoordinator stop];
3285
3286 NSString* title =
3287 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3288 NSString* message = l10n_util::GetNSString(
3289 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3290
3291 _alertCoordinator.reset([[AlertCoordinator alloc]
3292 initWithBaseViewController:self
3293 title:title
3294 message:message]);
3295
3296 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3297 action:nil
3298 style:UIAlertActionStyleCancel];
3299
3300 [_alertCoordinator
3301 addItemWithTitle:l10n_util::GetNSString(
3302 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3303 action:^{
3304 OpenUrlWithCompletionHandler(settingURL, nil);
3305 }
3306 style:UIAlertActionStyleDefault];
3307
3308 [_alertCoordinator start];
3309}
3310
3311- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3312 dispatch_async(dispatch_get_main_queue(), ^{
3313 NSString* title =
3314 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3315 [self showErrorAlertWithStringTitle:title message:errorContent];
3316 });
3317}
3318
3319// This callback is triggered when the image is effectively saved onto the photo
3320// album, or if the save failed for some reason.
3321- (void)finishSavingImageWithError:(NSError*)error {
3322 // Was there an error?
3323 if (error) {
3324 // Saving photo failed even though user has granted access to Photos.
3325 // Display the error information from the NSError object for user.
3326 NSString* errorMessage = [NSString
3327 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3328 [error domain], [error code]];
3329 // This code may be execute outside of the main thread. Make sure to display
3330 // the error on the main thread.
3331 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3332 } else {
3333 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3334 // the photo application. The current behaviour is to create the photo there
3335 // but not providing any link to it is suboptimal. That's what Safari is
3336 // doing, and what the PM want, but it doesn't make it right.
3337 }
3338}
3339
sdefresnee65fd872016-12-19 13:38:133340#pragma mark - Showing popups
3341
3342- (void)showToolsMenuPopup {
3343 DCHECK(_browserState);
3344 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133345
3346 // Dismiss the omnibox (if open).
3347 [_toolbarController cancelOmniboxEdit];
3348 // Dismiss the soft keyboard (if open).
3349 [[_model currentTab].webController dismissKeyboard];
3350 // Dismiss Find in Page focus.
3351 [self updateFindBar:NO shouldFocus:NO];
3352
liaoyukeea9f3ee62017-03-07 22:05:393353 base::scoped_nsobject<ToolsMenuConfiguration> configuration(
3354 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]]);
sdefresnee65fd872016-12-19 13:38:133355 if ([_model count] == 0)
liaoyukeea9f3ee62017-03-07 22:05:393356 [configuration setNoOpenedTabs:YES];
3357
sdefresnee65fd872016-12-19 13:38:133358 if (_isOffTheRecord)
liaoyukeea9f3ee62017-03-07 22:05:393359 [configuration setInIncognito:YES];
3360
sdefresnee65fd872016-12-19 13:38:133361 if (reading_list::switches::IsReadingListEnabled()) {
3362 if (!_readingListMenuNotifier) {
3363 _readingListMenuNotifier.reset([[ReadingListMenuNotifier alloc]
3364 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
3365 _browserState)]);
3366 }
liaoyukeea9f3ee62017-03-07 22:05:393367 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
sdefresnee65fd872016-12-19 13:38:133368 }
3369
liaoyukeea9f3ee62017-03-07 22:05:393370 [configuration setUserAgentType:self.userAgentType];
3371
3372 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
3373
sdefresnee65fd872016-12-19 13:38:133374 ToolsPopupController* toolsPopupController =
3375 [_toolbarController toolsPopupController];
3376 if ([_model currentTab]) {
3377 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
3378 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
3379 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
3380 [toolsPopupController setCanUseReaderMode:self.canUseReaderMode];
sdefresnee65fd872016-12-19 13:38:133381 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
3382
3383 if (!IsIPadIdiom())
3384 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
3385 }
3386}
3387
3388- (void)showPageInfoPopupForView:(UIView*)sourceView {
3389 Tab* tab = [_model currentTab];
3390 DCHECK([tab navigationManager]);
3391 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3392
3393 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3394 // be trigerred by a button enabled when a current item matches some
3395 // conditions. However a crash was seen were navItem was NULL hence this
3396 // test after a DCHECK.
3397 DCHECK(navItem);
3398 if (!navItem)
3399 return;
3400
olivierrobin013ba672017-03-01 21:16:243401 // Don't show if the page is native except for offline pages (to show the
3402 // offline page info).
3403 if ([self isTabNativePage:tab] &&
3404 !reading_list::IsOfflineURL(navItem->GetURL())) {
sdefresnee65fd872016-12-19 13:38:133405 return;
olivierrobin013ba672017-03-01 21:16:243406 }
sdefresnee65fd872016-12-19 13:38:133407
justincohenb1a73cf2017-02-06 20:25:433408 // Don't show the bubble twice (this can happen when tapping very quickly in
3409 // accessibility mode).
3410 if (_pageInfoController)
3411 return;
3412
sdefresnee65fd872016-12-19 13:38:133413 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3414
3415 // Dismiss the omnibox (if open).
3416 [_toolbarController cancelOmniboxEdit];
3417
3418 [[NSNotificationCenter defaultCenter]
3419 postNotificationName:ios_internal::kPageInfoWillShowNotification
3420 object:nil];
3421
3422 // TODO(rohitrao): Get rid of PageInfoModel completely.
3423 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3424 PageInfoModel* pageInfoModel = new PageInfoModel(
3425 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3426
3427 UIView* view = [self view];
3428 _pageInfoController.reset([[PageInfoViewController alloc]
3429 initWithModel:pageInfoModel
3430 bridge:bridge
3431 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
3432 parentView:view]);
3433 bridge->set_controller(_pageInfoController.get());
3434}
3435
3436- (void)hidePageInfoPopupForView:(UIView*)sourceView {
3437 [_pageInfoController dismiss];
3438 _pageInfoController.reset();
3439}
3440
3441- (void)showSecurityHelpPage {
3442 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3443 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133444 inBackground:NO
3445 appendTo:kCurrentTab];
3446 [self hidePageInfoPopupForView:nil];
3447}
3448
3449- (void)showTabHistoryPopupForBackwardHistory {
3450 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133451
3452 // Dismiss the omnibox (if open).
3453 [_toolbarController cancelOmniboxEdit];
3454 // Dismiss the soft keyboard (if open).
3455 Tab* tab = [_model currentTab];
3456 [tab.webController dismissKeyboard];
3457
kkhorimoto1c12cbf2017-03-14 02:57:133458 web::NavigationItemList backwardItems =
3459 [tab navigationManager]->GetBackwardItems();
sdefresnee65fd872016-12-19 13:38:133460 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133461 withItems:backwardItems
sdefresnee65fd872016-12-19 13:38:133462 forBackHistory:YES];
3463}
3464
3465- (void)showTabHistoryPopupForForwardHistory {
3466 DCHECK(self.visible || self.dismissingModal);
sdefresnee65fd872016-12-19 13:38:133467
3468 // Dismiss the omnibox (if open).
3469 [_toolbarController cancelOmniboxEdit];
3470 // Dismiss the soft keyboard (if open).
3471 Tab* tab = [_model currentTab];
3472 [tab.webController dismissKeyboard];
3473
kkhorimoto1c12cbf2017-03-14 02:57:133474 web::NavigationItemList forwardItems =
3475 [tab navigationManager]->GetForwardItems();
sdefresnee65fd872016-12-19 13:38:133476 [_toolbarController showTabHistoryPopupInView:[self view]
kkhorimoto1c12cbf2017-03-14 02:57:133477 withItems:forwardItems
sdefresnee65fd872016-12-19 13:38:133478 forBackHistory:NO];
3479}
3480
3481- (void)navigateToSelectedEntry:(id)sender {
3482 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
3483 TabHistoryCell* selectedCell = (TabHistoryCell*)sender;
kkhorimoto7aed9e262017-03-04 02:28:553484 [[_model currentTab] goToItem:selectedCell.item];
sdefresnee65fd872016-12-19 13:38:133485 [_toolbarController dismissTabHistoryPopup];
3486}
3487
3488- (void)print {
3489 Tab* currentTab = [_model currentTab];
3490 // The UI should prevent users from printing non-printable pages. However, a
3491 // redirection to an un-printable page can happen before it is reflected in
3492 // the UI.
3493 if (![currentTab viewForPrinting]) {
pinkerton07e27842017-03-02 15:29:023494 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
sdefresnee65fd872016-12-19 13:38:133495 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
3496 return;
3497 }
3498 DCHECK(_browserState);
3499 if (!_printController.get()) {
3500 _printController.reset([[PrintController alloc]
3501 initWithContextGetter:_browserState->GetRequestContext()]);
3502 }
3503 [_printController printView:[currentTab viewForPrinting]
3504 withTitle:[currentTab title]
3505 viewController:self];
3506}
3507
3508- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
3509 if (!reading_list::switches::IsReadingListEnabled()) {
3510 return;
3511 }
3512 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3513
3514 ReadingListModel* readingModel =
3515 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013516 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3517 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133518
pinkerton07e27842017-03-02 15:29:023519 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033520 [self showSnackbar:l10n_util::GetNSString(
3521 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133522}
3523
3524#pragma mark - Keyboard commands management
3525
3526- (BOOL)shouldRegisterKeyboardCommands {
3527 if ([self presentedViewController])
3528 return NO;
3529
3530 if (_voiceSearchController && _voiceSearchController->IsVisible())
3531 return NO;
3532
3533 // If there is no first responder, try to make the webview the first
3534 // responder.
3535 if (!GetFirstResponder()) {
3536 [_model.get().currentTab.webController.webViewProxy becomeFirstResponder];
3537 }
3538
3539 return YES;
3540}
3541
3542- (KeyCommandsProvider*)keyCommandsProvider {
3543 if (!_keyCommandsProvider) {
3544 _keyCommandsProvider.reset([_dependencyFactory newKeyCommandsProvider]);
3545 }
3546 return _keyCommandsProvider.get();
3547}
3548
3549#pragma mark - KeyCommandsPlumbing
3550
3551- (BOOL)isOffTheRecord {
3552 return _isOffTheRecord;
3553}
3554
3555- (NSUInteger)tabsCount {
3556 return [_model count];
3557}
3558
lpromero47ea8862017-01-13 17:51:063559- (BOOL)canGoBack {
3560 return [_model currentTab].canGoBack;
3561}
3562
3563- (BOOL)canGoForward {
3564 return [_model currentTab].canGoForward;
3565}
3566
sdefresnee65fd872016-12-19 13:38:133567- (void)focusTabAtIndex:(NSUInteger)index {
3568 if ([_model count] > index) {
3569 [_model setCurrentTab:[_model tabAtIndex:index]];
3570 }
3571}
3572
3573- (void)focusNextTab {
3574 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3575 NSInteger modelCount = [_model count];
3576 if (currentTabIndex < modelCount - 1) {
3577 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3578 [_model setCurrentTab:nextTab];
3579 } else {
3580 [_model setCurrentTab:[_model tabAtIndex:0]];
3581 }
3582}
3583
3584- (void)focusPreviousTab {
3585 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3586 if (currentTabIndex > 0) {
3587 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3588 [_model setCurrentTab:previousTab];
3589 } else {
3590 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3591 [_model setCurrentTab:lastTab];
3592 }
3593}
3594
3595- (void)reopenClosedTab {
3596 sessions::TabRestoreService* const tabRestoreService =
3597 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3598 if (!tabRestoreService || tabRestoreService->entries().empty())
3599 return;
3600
3601 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3602 tabRestoreService->entries().front();
3603 // Only handle the TAB type.
3604 if (entry->type != sessions::TabRestoreService::TAB)
3605 return;
3606
3607 [self chromeExecuteCommand:[GenericChromeCommand commandWithTag:IDC_NEW_TAB]];
3608 TabRestoreServiceDelegateImplIOS* const delegate =
3609 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3610 _browserState);
3611 tabRestoreService->RestoreEntryById(delegate, entry->id,
3612 WindowOpenDisposition::CURRENT_TAB);
3613}
3614
3615- (void)focusOmnibox {
3616 [_toolbarController focusOmnibox];
3617}
3618
3619#pragma mark - UIResponder
3620
3621- (NSArray*)keyCommands {
3622 if (![self shouldRegisterKeyboardCommands]) {
3623 return nil;
3624 }
3625 return [self.keyCommandsProvider
3626 keyCommandsForConsumer:self
3627 editingText:![self isFirstResponder]];
3628}
3629
3630#pragma mark -
3631
3632// Induce an intentional crash in the browser process.
3633- (void)induceBrowserCrash {
3634 CHECK(false);
3635 // Call another function, so that the above CHECK can't be tail-call
3636 // optimized. This ensures that this method's name will show up in the stack
3637 // for easier identification.
3638 CHECK(true);
3639}
3640
3641- (void)loadURL:(const GURL&)url
3642 referrer:(const web::Referrer&)referrer
3643 transition:(ui::PageTransition)transition
3644 rendererInitiated:(BOOL)rendererInitiated {
3645 [[OmniboxGeolocationController sharedInstance]
3646 locationBarDidSubmitURL:url
3647 transition:transition
3648 browserState:_browserState];
3649
3650 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3651 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3652 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3653 }
3654
3655 // NOTE: This check for the Crash Host URL is here to avoid the URL from
3656 // ending up in the history causign the app to crash at every subsequent
3657 // restart.
3658 if (url.host() == kChromeUIBrowserCrashHost) {
3659 [self induceBrowserCrash];
3660 // In debug the app can continue working even after the CHECK. Adding a
3661 // return avoids the crash url to be added to the history.
3662 return;
3663 }
3664
3665 if (url == [_preloadController prerenderedURL]) {
3666 Tab* oldTab = [_model currentTab];
3667 Tab* newTab = [_preloadController releasePrerenderContents];
3668 DCHECK(oldTab);
3669 DCHECK(newTab);
kkhorimotod804c5732017-03-15 23:44:523670 bool canPruneItems =
3671 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
3672 if (oldTab && newTab && canPruneItems) {
sdefresnee65fd872016-12-19 13:38:133673 [oldTab recordStateInHistory];
kkhorimotod804c5732017-03-15 23:44:523674 [newTab navigationManager]->CopyStateFromAndPrune(
3675 [oldTab navigationManager]);
sdefresnee65fd872016-12-19 13:38:133676 [[newTab nativeAppNavigationController]
3677 copyStateFrom:[oldTab nativeAppNavigationController]];
sdefresnee398e7fd2017-02-08 14:51:593678 [_model replaceTab:oldTab withTab:newTab];
sdefresnee65fd872016-12-19 13:38:133679
3680 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3681 // BrowserViewController to detect that a pre-rendered tab is switched in,
3682 // and show the prerendering animation.
3683 newTab.isPrerenderTab = NO;
3684
sdefresne2f7781c2017-03-02 19:12:463685 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133686
3687 return;
3688 }
3689 }
3690
3691 GURL urlToLoad = url;
3692 if ([_preloadController hasPrefetchedURL:url]) {
3693 // Prefetched URLs have modified URLs, so load the prefetched version of
3694 // |url| instead of the original |url|.
3695 urlToLoad = [_preloadController prefetchedURL];
3696 }
3697
3698 [_preloadController cancelPrerender];
3699
3700 // Some URLs are not allowed while in incognito. If we are in incognito and
3701 // load a disallowed URL, instead create a new tab not in the incognito state.
3702 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3703 [self webPageOrderedOpen:url
3704 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133705 inIncognito:NO
3706 inBackground:NO
3707 appendTo:kCurrentTab];
3708 return;
3709 }
3710
3711 web::NavigationManager::WebLoadParams params(urlToLoad);
3712 params.referrer = referrer;
3713 params.transition_type = transition;
3714 params.is_renderer_initiated = rendererInitiated;
3715 DCHECK([_model currentTab]);
3716 [[[_model currentTab] webController] loadWithParams:params];
3717}
3718
3719- (void)loadJavaScriptFromLocationBar:(NSString*)script {
3720 [_preloadController cancelPrerender];
3721 DCHECK([_model currentTab]);
3722 [[_model currentTab].webController executeUserJavaScript:script
3723 completionHandler:nil];
3724}
3725
3726- (web::WebState*)currentWebState {
3727 return [[_model currentTab] webState];
3728}
3729
3730// This is called from within an animation block.
3731- (void)toolbarHeightChanged {
3732 if ([self headerHeight] != 0) {
3733 // Ensure full screen height is updated.
3734 Tab* currentTab = [_model currentTab];
3735 BOOL visible = self.isToolbarOnScreen;
3736 [currentTab updateFullscreenWithToolbarVisible:visible];
3737 }
3738}
3739
3740// Load a new URL on a new page/tab.
3741- (void)webPageOrderedOpen:(const GURL&)URL
3742 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133743 inBackground:(BOOL)inBackground
3744 appendTo:(OpenPosition)appendTo {
3745 Tab* adjacentTab = nil;
3746 if (appendTo == kCurrentTab)
3747 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353748 [_model insertTabWithURL:URL
3749 referrer:referrer
3750 transition:ui::PAGE_TRANSITION_LINK
3751 opener:adjacentTab
3752 openedByDOM:NO
3753 atIndex:TabModelConstants::kTabPositionAutomatically
3754 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133755}
3756
3757- (void)webPageOrderedOpen:(const GURL&)url
3758 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133759 inIncognito:(BOOL)inIncognito
3760 inBackground:(BOOL)inBackground
3761 appendTo:(OpenPosition)appendTo {
3762 if (inIncognito == _isOffTheRecord) {
3763 [self webPageOrderedOpen:url
3764 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133765 inBackground:inBackground
3766 appendTo:appendTo];
3767 return;
3768 }
3769 // When sending an open command that switches modes, ensure the tab
3770 // ends up appended to the end of the model, not just next to what is
3771 // currently selected in the other mode. This is done with the |append|
3772 // parameter.
3773 base::scoped_nsobject<OpenUrlCommand> command([[OpenUrlCommand alloc]
3774 initWithURL:url
3775 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133776 inIncognito:inIncognito
3777 inBackground:inBackground
3778 appendTo:kLastTab]);
3779 [self chromeExecuteCommand:command];
3780}
3781
3782- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
3783 [[_model currentTab] loadSessionTab:sessionTab];
3784}
3785
3786- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433787 DCHECK(javascript);
3788 javascript = [javascript stringByRemovingPercentEncoding];
3789 web::WebState* webState = [[_model currentTab] webState];
3790 if (webState) {
3791 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3792 }
sdefresnee65fd872016-12-19 13:38:133793}
3794
3795#pragma mark - WebToolbarDelegate methods
3796
3797- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3798 if (_locationBarHasFocus)
3799 return; // TODO(crbug.com/244366): This should not be necessary.
3800 _locationBarHasFocus = YES;
3801 [[NSNotificationCenter defaultCenter]
3802 postNotificationName:ios_internal::
3803 kLocationBarBecomesFirstResponderNotification
3804 object:nil];
3805 [_sideSwipeController setEnabled:NO];
3806 if ([[_model currentTab].webController wantsKeyboardShield]) {
3807 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3808 [_typingShield setAlpha:0.0];
3809 [_typingShield setHidden:NO];
3810 [UIView animateWithDuration:0.3
3811 animations:^{
3812 [_typingShield setAlpha:1.0];
3813 }];
3814 }
3815 [[OmniboxGeolocationController sharedInstance]
3816 locationBarDidBecomeFirstResponder:_browserState];
3817}
3818
3819- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3820 if (!_locationBarHasFocus)
3821 return; // TODO(crbug.com/244366): This should not be necessary.
3822 _locationBarHasFocus = NO;
3823 [_sideSwipeController setEnabled:YES];
3824 [[NSNotificationCenter defaultCenter]
3825 postNotificationName:ios_internal::
3826 kLocationBarResignsFirstResponderNotification
3827 object:nil];
3828 [UIView animateWithDuration:0.3
3829 animations:^{
3830 [_typingShield setAlpha:0.0];
3831 }
3832 completion:^(BOOL finished) {
3833 // This can happen if one quickly resigns the omnibox and then taps
3834 // on the omnibox again during this animation. If the animation is
3835 // interrupted and the toolbar controller is first responder, it's safe
3836 // to assume the |_typingShield| shouldn't be hidden here.
3837 if (!finished && [_toolbarController isOmniboxFirstResponder])
3838 return;
3839 [_typingShield setHidden:YES];
3840 }];
3841 [[OmniboxGeolocationController sharedInstance]
3842 locationBarDidResignFirstResponder:_browserState];
3843
3844 // If a load was cancelled by an omnibox edit, but nothing is loading when
3845 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3846 if (_locationBarEditCancelledLoad) {
3847 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:293848
3849 web::WebState* webState = [_model currentTab].webState;
3850 if (!_toolbarModelIOS->IsLoading() && webState)
3851 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3852 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133853 }
3854}
3855
3856- (IBAction)locationBarBeganEdit:(id)sender {
3857 // On handsets, if a page is currently loading it should be stopped.
3858 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
3859 base::scoped_nsobject<GenericChromeCommand> command(
3860 [[GenericChromeCommand alloc] initWithTag:IDC_STOP]);
3861 [self chromeExecuteCommand:command];
3862 _locationBarEditCancelledLoad = YES;
3863 }
3864}
3865
3866- (IBAction)prepareToEnterTabSwitcher:(id)sender {
3867 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
3868}
3869
3870- (ToolbarModelIOS*)toolbarModelIOS {
3871 return _toolbarModelIOS.get();
3872}
3873
3874- (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
3875 [_toolbarController setBackgroundAlpha:alpha];
3876}
3877
3878- (void)updateToolbarControlsAlpha:(CGFloat)alpha {
3879 [_toolbarController setControlsAlpha:alpha];
3880}
3881
3882- (void)willUpdateToolbarSnapshot {
3883 [[_model currentTab].overscrollActionsController clear];
3884}
3885
3886- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
3887 CGRect frame = [_contentArea frame];
3888 if (!fullScreen) {
3889 // Changing the origin here is unnecessary, it's set in page_animation_util.
3890 frame.size.height -= [self headerHeight];
3891 }
3892
3893 CGFloat shortAxis = frame.size.width;
3894 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
3895 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
3896 CGFloat aspectRatio = frame.size.height / frame.size.width;
3897 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
3898 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
3899 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
3900 CGRect cardFrame = {frame.origin, cardSize};
3901
3902 CardView* card =
3903 [[[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord]
3904 autorelease];
3905 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
3906 : CardCloseButtonSide::LEADING;
3907 [_contentArea addSubview:card];
3908 return card;
3909}
3910
3911#pragma mark - Command Handling
3912
3913- (IBAction)chromeExecuteCommand:(id)sender {
3914 NSInteger command = [sender tag];
3915
3916 if (!_model || !_browserState)
3917 return;
rohitrao005a6432017-03-16 20:52:423918 Tab* currentTab = [_model currentTab];
sdefresnee65fd872016-12-19 13:38:133919
3920 switch (command) {
3921 case IDC_BACK:
lpromero47ea8862017-01-13 17:51:063922 [[_model currentTab] goBack];
sdefresnee65fd872016-12-19 13:38:133923 break;
3924 case IDC_BOOKMARK_PAGE:
3925 [self initializeBookmarkInteractionController];
3926 [_bookmarkInteractionController
3927 presentBookmarkForTab:[_model currentTab]
3928 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
3929 inView:[_toolbarController bookmarkButtonView]
3930 originRect:[_toolbarController bookmarkButtonAnchorRect]];
3931 break;
3932 case IDC_CLOSE_TAB:
3933 [self closeCurrentTab];
3934 break;
3935 case IDC_FIND:
3936 [self initFindBarForTab];
3937 break;
rohitraob2bf3cb2017-02-10 14:10:363938 case IDC_FIND_NEXT: {
rohitrao005a6432017-03-16 20:52:423939 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:133940 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:423941 FindTabHelper::FromWebState(currentTab.webState)
3942 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
3943 [_findBarController updateResultsCount:model];
3944 });
sdefresnee65fd872016-12-19 13:38:133945 break;
rohitraob2bf3cb2017-02-10 14:10:363946 }
3947 case IDC_FIND_PREVIOUS: {
rohitrao005a6432017-03-16 20:52:423948 DCHECK(currentTab);
sdefresnee65fd872016-12-19 13:38:133949 // TODO(crbug.com/603524): Reshow find bar if necessary.
rohitrao005a6432017-03-16 20:52:423950 FindTabHelper::FromWebState(currentTab.webState)
3951 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
3952 [_findBarController updateResultsCount:model];
3953 });
sdefresnee65fd872016-12-19 13:38:133954 break;
rohitraob2bf3cb2017-02-10 14:10:363955 }
sdefresnee65fd872016-12-19 13:38:133956 case IDC_FIND_CLOSE:
3957 [self closeFindInPage];
3958 break;
3959 case IDC_FIND_UPDATE:
3960 [self searchFindInPage];
3961 break;
3962 case IDC_FORWARD:
lpromero47ea8862017-01-13 17:51:063963 [[_model currentTab] goForward];
sdefresnee65fd872016-12-19 13:38:133964 break;
3965 case IDC_FULLSCREEN:
3966 NOTIMPLEMENTED();
3967 break;
3968 case IDC_HELP_PAGE_VIA_MENU:
3969 [self showHelpPage];
3970 break;
3971 case IDC_NEW_TAB:
3972 if (_isOffTheRecord) {
3973 // Not for this browser state, send it on its way.
3974 [super chromeExecuteCommand:sender];
3975 } else {
3976 [self newTab:sender];
3977 }
3978 break;
3979 case IDC_PRELOAD_VOICE_SEARCH:
3980 // Preload VoiceSearchController and views and view controllers needed
3981 // for voice search.
3982 [self ensureVoiceSearchControllerCreated];
3983 _voiceSearchController->PrepareToAppear();
3984 break;
3985 case IDC_NEW_INCOGNITO_TAB:
3986 if (_isOffTheRecord) {
3987 [self newTab:sender];
3988 } else {
3989 // Not for this browser state, send it on its way.
3990 [super chromeExecuteCommand:sender];
3991 }
3992 break;
liaoyuke563dc4a2017-03-17 18:36:293993 case IDC_RELOAD: {
3994 web::WebState* webState = [_model currentTab].webState;
3995 if (webState)
3996 // |check_for_repost| is true because the reload is explicitly initiated
3997 // by the user.
3998 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3999 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134000 break;
liaoyuke563dc4a2017-03-17 18:36:294001 }
sdefresnee65fd872016-12-19 13:38:134002 case IDC_SHARE_PAGE:
4003 [self sharePage];
4004 break;
4005 case IDC_SHOW_MAIL_COMPOSER:
4006 [self showMailComposer:sender];
4007 break;
4008 case IDC_READER_MODE:
4009 [[_model currentTab] switchToReaderMode];
4010 break;
4011 case IDC_REQUEST_DESKTOP_SITE:
4012 [self enableDesktopUserAgent];
4013 break;
liaoyukeea9f3ee62017-03-07 22:05:394014 case IDC_REQUEST_MOBILE_SITE:
4015 [self enableMobileUserAgent];
4016 break;
sdefresnee65fd872016-12-19 13:38:134017 case IDC_SHOW_TOOLS_MENU: {
jif7fed8122017-02-08 13:15:254018 [self showToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134019 break;
4020 }
4021 case IDC_SHOW_BOOKMARK_MANAGER: {
4022 if (IsIPadIdiom()) {
4023 [self showAllBookmarks];
4024 } else {
4025 [self initializeBookmarkInteractionController];
4026 [_bookmarkInteractionController presentBookmarks];
4027 }
4028 break;
4029 }
4030 case IDC_SHOW_OTHER_DEVICES: {
4031 if (IsIPadIdiom()) {
4032 [self showNTPPanel:NewTabPage::kOpenTabsPanel];
4033 } else {
4034 UIViewController* controller = [RecentTabsPanelViewController
4035 controllerToPresentForBrowserState:_browserState
4036 loader:self];
4037 controller.modalPresentationStyle = UIModalPresentationFormSheet;
4038 controller.modalPresentationCapturesStatusBarAppearance = YES;
4039 [self presentViewController:controller animated:YES completion:nil];
4040 }
4041 break;
4042 }
4043 case IDC_STOP:
4044 [[_model currentTab] stopLoading];
4045 break;
4046#if !defined(NDEBUG)
4047 case IDC_VIEW_SOURCE:
4048 [self viewSource];
4049 break;
4050#endif
4051 case IDC_SHOW_PAGE_INFO:
4052 DCHECK([sender isKindOfClass:[UIButton class]]);
4053 [self showPageInfoPopupForView:sender];
4054 break;
4055 case IDC_HIDE_PAGE_INFO:
4056 [[NSNotificationCenter defaultCenter]
4057 postNotificationName:ios_internal::kPageInfoWillHideNotification
4058 object:nil];
4059 [self hidePageInfoPopupForView:sender];
4060 break;
4061 case IDC_SHOW_SECURITY_HELP:
4062 [self showSecurityHelpPage];
4063 break;
4064 case IDC_SHOW_BACK_HISTORY:
4065 [self showTabHistoryPopupForBackwardHistory];
4066 break;
4067 case IDC_SHOW_FORWARD_HISTORY:
4068 [self showTabHistoryPopupForForwardHistory];
4069 break;
4070 case IDC_BACK_FORWARD_IN_TAB_HISTORY:
4071 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
4072 [self navigateToSelectedEntry:sender];
4073 break;
4074 case IDC_PRINT:
4075 [self print];
4076 break;
4077 case IDC_ADD_READING_LIST: {
4078 DCHECK(reading_list::switches::IsReadingListEnabled());
4079 ReadingListAddCommand* command =
4080 base::mac::ObjCCastStrict<ReadingListAddCommand>(sender);
4081 [self addToReadingListURL:[command URL] title:[command title]];
4082 break;
4083 }
4084 case IDC_RATE_THIS_APP:
4085 [self showRateThisAppDialog];
4086 break;
4087 case IDC_SHOW_READING_LIST:
4088 DCHECK(reading_list::switches::IsReadingListEnabled());
4089 [self showReadingList];
4090 break;
4091 case IDC_VOICE_SEARCH:
4092 // If the voice search command is coming from a UIView sender, store it
4093 // before sending the command up the responder chain.
4094 if ([sender isKindOfClass:[UIView class]])
4095 _voiceSearchButton.reset(sender);
4096 [super chromeExecuteCommand:sender];
4097 break;
4098 case IDC_SHOW_QR_SCANNER:
jif5f067c62017-02-03 17:36:434099 [self showQRScanner];
sdefresnee65fd872016-12-19 13:38:134100 break;
gambardd2e44fb2017-01-25 09:14:214101 case IDC_SHOW_SUGGESTIONS:
4102 if (experimental_flags::IsSuggestionsUIEnabled()) {
4103 [self showSuggestionsUI];
4104 }
4105 break;
sdefresnee65fd872016-12-19 13:38:134106 default:
4107 // Unknown commands get sent up the responder chain.
4108 [super chromeExecuteCommand:sender];
4109 break;
4110 }
4111}
4112
4113- (void)closeCurrentTab {
4114 Tab* currentTab = [_model currentTab];
4115 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4116 if (tabIndex == NSNotFound)
4117 return;
4118
jif7fed8122017-02-08 13:15:254119 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4120 // iPad.
sdefresnee65fd872016-12-19 13:38:134121 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4122 exitingPage.image =
4123 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4124
4125 // Close the actual tab, and add its image as a subview.
4126 [_model closeTabAtIndex:tabIndex];
4127
4128 // Do not animate close in iPad.
4129 if (!IsIPadIdiom()) {
4130 [_contentArea addSubview:exitingPage];
4131 ios_internal::page_animation_util::AnimateOutWithCompletion(
4132 exitingPage, 0, YES, IsPortrait(), ^{
4133 [exitingPage removeFromSuperview];
4134 });
4135 }
4136}
4137
sdefresnee65fd872016-12-19 13:38:134138- (void)sharePage {
jif4a8cf942017-02-03 12:05:244139 ShareToData* data = activity_services::ShareToDataForTab([_model currentTab]);
sdefresnee65fd872016-12-19 13:38:134140 if (data)
4141 [self sharePageWithData:data];
4142}
4143
4144- (void)sharePageWithData:(ShareToData*)data {
4145 id<ShareProtocol> controller = [_dependencyFactory shareControllerInstance];
4146 if ([controller isActive])
4147 return;
4148 CGRect fromRect = [_toolbarController shareButtonAnchorRect];
4149 UIView* inView = [_toolbarController shareButtonView];
4150 [controller shareWithData:data
4151 controller:self
4152 browserState:_browserState
4153 shareToDelegate:self
4154 fromRect:fromRect
4155 inView:inView];
4156}
4157
4158- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
4159 [[_dependencyFactory shareControllerInstance] cancelShareAnimated:NO];
4160 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4161 [_bookmarkInteractionController dismissSnackbar];
4162 [_toolbarController cancelOmniboxEdit];
4163 [_dialogPresenter cancelAllDialogs];
4164 [self hidePageInfoPopupForView:nil];
4165 if (_voiceSearchController)
4166 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364167
4168 Tab* currentTab = [_model currentTab];
4169 [currentTab dismissModals];
4170
rohitrao005a6432017-03-16 20:52:424171 if (currentTab) {
4172 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4173 if (findHelper) {
4174 findHelper->StopFinding(^{
4175 [self updateFindBar:NO shouldFocus:NO];
4176 });
4177 }
4178 }
rohitraob2bf3cb2017-02-10 14:10:364179
sdefresnee65fd872016-12-19 13:38:134180 [_contextualSearchController movePanelOffscreen];
sdefresnee65fd872016-12-19 13:38:134181 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134182 [_printController dismissAnimated:YES];
4183 _printController.reset();
jif7fed8122017-02-08 13:15:254184 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134185 [_contextMenuCoordinator stop];
4186 [self dismissRateThisAppDialog];
4187
gambardd2e44fb2017-01-25 09:14:214188 [_contentSuggestionsCoordinator stop];
4189
sdefresnee65fd872016-12-19 13:38:134190 if (self.presentedViewController) {
4191 // Dismisses any other modal controllers that may be present, e.g. Recent
4192 // Tabs.
4193 // Note that currently, some controllers like the bookmark ones were already
4194 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4195 // but are still reported as the presentedViewController. The result is that
4196 // this will call -dismissViewControllerAnimated:completion: a second time
4197 // on it. It is not per se an issue, as it is a no-op. The problem is that
4198 // in such a case, the completion block is not called.
4199 // To ensure the completion is called, nil is passed here, and the
4200 // completion is called below.
4201 [self dismissViewControllerAnimated:NO completion:nil];
4202 // Dismissed controllers will be so after a delay. Queue the completion
4203 // callback after that.
4204 if (completion) {
4205 dispatch_after(
4206 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4207 dispatch_get_main_queue(), ^{
4208 completion();
4209 });
4210 }
4211 } else if (completion) {
4212 // If no view controllers are presented, we should be ok with dispatching
4213 // the completion block directly.
4214 dispatch_async(dispatch_get_main_queue(), completion);
4215 }
4216}
4217
4218- (void)showHelpPage {
4219 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4220 [self webPageOrderedOpen:helpUrl
4221 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134222 inBackground:NO
4223 appendTo:kCurrentTab];
4224}
4225
4226- (void)enableDesktopUserAgent {
4227 [[_model currentTab] enableDesktopUserAgent];
4228 [[_model currentTab] reloadForDesktopUserAgent];
4229}
4230
liaoyukeea9f3ee62017-03-07 22:05:394231// TODO(crbug.com/692303): Implement the actual functionality of
4232// "Request Mobile Site", and also refactoring the user agent related function
4233// names to improve readability.
4234- (void)enableMobileUserAgent {
4235}
4236
sdefresnee65fd872016-12-19 13:38:134237- (void)resetAllWebViews {
eugenebutf8a138e62017-01-24 22:41:344238 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:134239 [_model resetAllWebViews];
4240}
4241
4242#pragma mark - Find Bar
4243
4244- (void)hideFindBarWithAnimation:(BOOL)animate {
4245 [_findBarController hideFindBarView:animate];
4246}
4247
4248- (void)showFindBarWithAnimation:(BOOL)animate
4249 selectText:(BOOL)selectText
4250 shouldFocus:(BOOL)shouldFocus {
4251 DCHECK(_findBarController);
4252 Tab* tab = [_model currentTab];
4253 DCHECK(tab);
4254 CRWWebController* webController = tab.webController;
4255
4256 CGRect referenceFrame = CGRectZero;
4257 if (IsIPadIdiom()) {
4258 referenceFrame = webController.visibleFrame;
4259 referenceFrame.origin.y -= kIPadFindBarOverlap;
4260 } else {
4261 referenceFrame = _contentArea.frame;
4262 }
4263
4264 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4265 [_findBarController addFindBarView:animate
4266 intoView:self.view
4267 withFrame:referenceFrame
4268 alignWithFrame:omniboxFrame
4269 selectText:selectText];
4270 [self updateFindBar:YES shouldFocus:shouldFocus];
4271}
4272
4273// Create find bar controller and pass it to the web controller.
4274- (void)initFindBarForTab {
4275 if (!self.canShowFindBar)
4276 return;
4277
4278 if (!_findBarController)
4279 _findBarController.reset(
4280 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord]);
4281
4282 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:424283 DCHECK(tab);
4284 auto* helper = FindTabHelper::FromWebState(tab.webState);
4285 DCHECK(!helper->IsFindUIActive());
4286 helper->SetFindUIActive(true);
sdefresnee65fd872016-12-19 13:38:134287 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4288}
4289
4290- (void)searchFindInPage {
rohitrao005a6432017-03-16 20:52:424291 DCHECK([_model currentTab]);
4292 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
sdefresnee65fd872016-12-19 13:38:134293 base::WeakNSObject<BrowserViewController> weakSelf(self);
rohitrao005a6432017-03-16 20:52:424294 helper->StartFinding([_findBarController searchTerm],
4295 ^(FindInPageModel* model) {
rohitraob2bf3cb2017-02-10 14:10:364296 [_findBarController updateResultsCount:model];
rohitrao005a6432017-03-16 20:52:424297 });
4298
sdefresnee65fd872016-12-19 13:38:134299 if (!_isOffTheRecord)
rohitrao005a6432017-03-16 20:52:424300 helper->PersistSearchTerm();
sdefresnee65fd872016-12-19 13:38:134301}
4302
4303- (void)closeFindInPage {
4304 base::WeakNSObject<BrowserViewController> weakSelf(self);
rohitrao005a6432017-03-16 20:52:424305 Tab* currentTab = [_model currentTab];
4306 if (currentTab) {
4307 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4308 [weakSelf updateFindBar:NO shouldFocus:NO];
4309 });
4310 }
sdefresnee65fd872016-12-19 13:38:134311}
4312
4313- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
rohitrao005a6432017-03-16 20:52:424314 DCHECK([_model currentTab]);
4315 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4316 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134317 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424318 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134319 }
4320
4321 [self setFramesForHeaders:[self headerViews]
4322 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424323 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134324 initialUpdate:initialUpdate
4325 focusTextfield:shouldFocus];
4326 } else {
4327 [self hideFindBarWithAnimation:YES];
4328 }
4329}
4330
4331- (void)showAllBookmarks {
4332 DCHECK(self.visible || self.dismissingModal);
4333 GURL URL(kChromeUIBookmarksURL);
4334 Tab* tab = [_model currentTab];
4335 web::NavigationManager::WebLoadParams params(URL);
4336 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
4337 [[tab webController] loadWithParams:params];
4338}
4339
4340- (void)showReadingList {
4341 DCHECK(reading_list::switches::IsReadingListEnabled());
gambard6299cc1d2017-02-21 13:06:034342 _readingListCoordinator.reset([[ReadingListCoordinator alloc]
4343 initWithBaseViewController:self
4344 browserState:self.browserState
4345 loader:self]);
4346
4347 [_readingListCoordinator start];
sdefresnee65fd872016-12-19 13:38:134348}
4349
4350- (void)showQRScanner {
4351 _qrScannerViewController.reset(
4352 [[QRScannerViewController alloc] initWithDelegate:_toolbarController]);
4353 [self presentViewController:[_qrScannerViewController
4354 getViewControllerToPresent]
4355 animated:YES
4356 completion:nil];
4357}
4358
gambardd2e44fb2017-01-25 09:14:214359- (void)showSuggestionsUI {
4360 if (!_contentSuggestionsCoordinator) {
4361 _contentSuggestionsCoordinator.reset([[ContentSuggestionsCoordinator alloc]
4362 initWithBaseViewController:self]);
gambard9b6abde2017-02-20 12:13:554363 [_contentSuggestionsCoordinator setURLLoader:self];
gambardd2e44fb2017-01-25 09:14:214364 }
gambard0ac7f3e2017-02-01 14:53:144365 [_contentSuggestionsCoordinator setBrowserState:_browserState];
gambardd2e44fb2017-01-25 09:14:214366 [_contentSuggestionsCoordinator start];
4367}
4368
sdefresnee65fd872016-12-19 13:38:134369- (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel {
4370 DCHECK(self.visible || self.dismissingModal);
4371 GURL url(kChromeUINewTabURL);
4372 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4373 if (fragment != "") {
4374 GURL::Replacements replacement;
4375 replacement.SetRefStr(fragment);
4376 url = url.ReplaceComponents(replacement);
4377 }
4378 Tab* tab = [_model currentTab];
4379 web::NavigationManager::WebLoadParams params(url);
4380 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
4381 [[tab webController] loadWithParams:params];
4382}
4383
4384- (void)showRateThisAppDialog {
4385 DCHECK(!_rateThisAppDialog.get());
4386
4387 // Store the current timestamp whenever this dialog is shown.
4388 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4389 base::Time::Now().ToInternalValue());
4390
4391 // Some versions of iOS7 do not support linking directly to the "Ratings and
4392 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4393 // links to the main appstore page for the Chrome app.
4394 NSURL* storeURL =
4395 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4396 @"MZStore.woa/wa/"
4397 @"viewContentsUserReviews?type=Purple+Software&id="
4398 @"535886823&pt=9008&ct=rating")];
4399
4400 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4401 [self clearPresentedStateWithCompletion:nil];
4402
4403 _rateThisAppDialog.reset(
4404 ios::GetChromeBrowserProvider()->CreateAppRatingPrompt());
4405 [_rateThisAppDialog setAppStoreURL:storeURL];
4406 [_rateThisAppDialog setDelegate:self];
4407 [_rateThisAppDialog show];
4408}
4409
4410- (void)dismissRateThisAppDialog {
4411 if (_rateThisAppDialog.get()) {
4412 base::RecordAction(base::UserMetricsAction(
4413 "IOSRateThisAppDialogDismissedProgramatically"));
4414 [_rateThisAppDialog dismiss];
4415 _rateThisAppDialog.reset();
4416 }
4417}
4418
4419#if !defined(NDEBUG)
4420- (void)viewSource {
4421 Tab* tab = [_model currentTab];
4422 DCHECK(tab);
4423 CRWWebController* webController = tab.webController;
4424 NSString* script = @"document.documentElement.outerHTML;";
4425 base::WeakNSObject<Tab> weakTab(tab);
eugenebut91cfb3a2017-02-21 16:40:314426 base::WeakNSObject<BrowserViewController> weakSelf(self);
sdefresnee65fd872016-12-19 13:38:134427 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4428 base::scoped_nsobject<Tab> strongTab([weakTab retain]);
4429 if (!strongTab)
4430 return;
4431 if (![result isKindOfClass:[NSString class]])
4432 result = @"Not an HTML page";
4433 std::string base64HTML;
4434 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4435 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
4436 web::Referrer referrer([strongTab url], web::ReferrerPolicyDefault);
eugenebut91cfb3a2017-02-21 16:40:314437
4438 [[weakSelf tabModel]
sdefresnea6395912017-03-01 01:14:354439 insertTabWithURL:URL
4440 referrer:referrer
4441 transition:ui::PAGE_TRANSITION_LINK
4442 opener:strongTab
4443 openedByDOM:YES
4444 atIndex:TabModelConstants::kTabPositionAutomatically
4445 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:134446 };
4447 [webController executeJavaScript:script
4448 completionHandler:completionHandlerBlock];
4449}
4450#endif // !defined(NDEBUG)
4451
4452- (void)startVoiceSearch {
4453 // Delay Voice Search until new tab animations have finished.
4454 if (_inNewTabAnimation) {
4455 _startVoiceSearchAfterNewTabAnimation = YES;
4456 return;
4457 }
4458
4459 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4460 // dismiss the keyboard.
4461 [self closeFindInPage];
4462 [[_model currentTab].webController dismissKeyboard];
4463
4464 // Ensure that voice search objects are created.
4465 [self ensureVoiceSearchControllerCreated];
4466 [self ensureVoiceSearchBarCreated];
4467
4468 // Present voice search.
4469 [_voiceSearchBar prepareToPresentVoiceSearch];
4470 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4471 [_toolbarController cancelOmniboxEdit];
4472}
4473
4474#pragma mark - ToolbarOwner
4475
4476- (ToolbarController*)relinquishedToolbarController {
4477 if (_isToolbarControllerRelinquished)
4478 return nil;
4479
4480 ToolbarController* relinquishedToolbarController = nil;
4481 if ([_toolbarController view].hidden) {
4482 Tab* currentTab = [_model currentTab];
4483 if (currentTab && UrlHasChromeScheme(currentTab.url)) {
4484 // Use the native content controller's toolbar when the BVC's is hidden.
4485 id nativeController = [self nativeControllerForTab:currentTab];
4486 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4487 relinquishedToolbarController =
4488 [nativeController relinquishedToolbarController];
4489 _relinquishedToolbarOwner.reset(nativeController);
4490 }
4491 }
4492 } else {
4493 relinquishedToolbarController = _toolbarController.get();
4494 }
4495 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4496 return relinquishedToolbarController;
4497}
4498
4499- (void)reparentToolbarController {
4500 if (_isToolbarControllerRelinquished) {
4501 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4502 // A native content controller's toolbar has been relinquished.
4503 [_relinquishedToolbarOwner reparentToolbarController];
4504 _relinquishedToolbarOwner.reset();
4505 } else if ([_findBarController isFindInPageShown]) {
4506 [self.view insertSubview:[_toolbarController view]
4507 belowSubview:[_findBarController view]];
4508 } else {
4509 [self.view addSubview:[_toolbarController view]];
4510 }
4511 if (_contextualSearchPanel) {
4512 // Move panel back into its correct place.
4513 [self.view insertSubview:_contextualSearchPanel
4514 aboveSubview:[_toolbarController view]];
4515 }
4516 _isToolbarControllerRelinquished = NO;
4517 }
4518}
4519
4520#pragma mark - TabModelObserver methods
4521
4522// Observer method, tab inserted.
4523- (void)tabModel:(TabModel*)model
4524 didInsertTab:(Tab*)tab
4525 atIndex:(NSUInteger)modelIndex
4526 inForeground:(BOOL)fg {
4527 DCHECK(tab);
4528 [self installDelegatesForTab:tab];
4529
4530 if (fg) {
4531 [_contextualSearchController setTab:tab];
4532 [_paymentRequestManager setWebState:tab.webState];
4533 }
4534}
4535
4536// Observer method, active tab changed.
4537- (void)tabModel:(TabModel*)model
4538 didChangeActiveTab:(Tab*)newTab
4539 previousTab:(Tab*)previousTab
4540 atIndex:(NSUInteger)index {
4541 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4542 // Currently this observer method is always called with a non-nil |newTab|,
4543 // but that may change in the future. Remove this DCHECK when it does.
4544 DCHECK(newTab);
4545 if (_infoBarContainer.get()) {
4546 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4547 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4548 }
4549 [self updateVoiceSearchBarVisibilityAnimated:NO];
4550
4551 [_contextualSearchController setTab:newTab];
4552 [_paymentRequestManager setWebState:newTab.webState];
4553
4554 [self tabSelected:newTab];
4555 DCHECK_EQ(newTab, [model currentTab]);
4556 [self installDelegatesForTab:newTab];
4557}
4558
4559// Observer method, tab changed.
4560- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4561 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4562 if (tab == [_model currentTab]) {
4563 [self updateToolbar];
4564 // Disable contextual search when |tab| is a voice search result tab.
4565 BOOL enableContextualSearch = self.active && !tab.isVoiceSearchResultsTab;
4566 [_contextualSearchController enableContextualSearch:enableContextualSearch];
4567 }
4568}
4569
sdefresne49cf2862017-03-15 13:46:144570// Observer method, tab replaced.
4571- (void)tabModel:(TabModel*)model
4572 didReplaceTab:(Tab*)oldTab
4573 withTab:(Tab*)newTab
4574 atIndex:(NSUInteger)index {
4575 [self uninstallDelegatesForTab:oldTab];
4576 [self installDelegatesForTab:newTab];
4577}
4578
sdefresnee65fd872016-12-19 13:38:134579// A tab has been removed, remove its views from display if necessary.
4580- (void)tabModel:(TabModel*)model
4581 didRemoveTab:(Tab*)tab
4582 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144583 [self uninstallDelegatesForTab:tab];
4584
sdefresnee65fd872016-12-19 13:38:134585 // Remove stored native controllers for the tab.
4586 [_nativeControllersForTabIDs removeObjectForKey:tab.tabId];
4587
4588 // Ignore changes while the tab stack view is visible (or while suspended).
4589 // The display will be refreshed when this view becomes active again.
4590 if (!self.visible || !model.webUsageEnabled)
4591 return;
4592
4593 // Remove the find bar for now.
4594 [self hideFindBarWithAnimation:NO];
4595}
4596
4597- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4598 if (tab == [model currentTab]) {
4599 [_contentArea displayContentView:nil];
4600 [_toolbarController selectedTabChanged];
4601 }
4602
4603 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4604 if ([model count] == 1) { // About to remove the last tab.
4605 [_contextualSearchController setTab:nil];
4606 [_paymentRequestManager setWebState:nil];
4607 }
4608}
4609
4610// Called when the number of tabs changes. Update the toolbar accordingly.
4611- (void)tabModelDidChangeTabCount:(TabModel*)model {
4612 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134613 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134614}
4615
4616#pragma mark - Upgrade Detection
4617
4618- (void)showUpgrade:(UpgradeCenter*)center {
4619 // Add an infobar on all the open tabs.
4620 for (Tab* tab in _model.get()) {
4621 NSString* tabId = tab.tabId;
4622 DCHECK([tab infoBarManager]);
4623 [center addInfoBarToManager:[tab infoBarManager] forTabId:tabId];
4624 }
4625}
4626
4627#pragma mark - ContextualSearchControllerDelegate
4628
4629- (void)createTabFromContextualSearchController:(const GURL&)url {
4630 Tab* currentTab = [_model currentTab];
4631 DCHECK(currentTab);
4632 NSUInteger index = [_model indexOfTab:currentTab];
4633 [self addSelectedTabWithURL:url
4634 atIndex:index + 1
4635 transition:ui::PAGE_TRANSITION_LINK];
4636}
4637
4638- (void)promotePanelToTabProvidedBy:(id<ContextualSearchTabProvider>)tabProvider
4639 focusInput:(BOOL)focusInput {
4640 // Tell the panel it will be promoted.
4641 ContextualSearchPanelView* promotingPanel = _contextualSearchPanel;
4642 [promotingPanel prepareForPromotion];
4643
4644 // Make a new panel and tell the controller about it.
4645 _contextualSearchPanel = [self createPanelView];
4646 [self.view insertSubview:_contextualSearchPanel belowSubview:promotingPanel];
4647 [_contextualSearchController setPanel:_contextualSearchPanel];
4648
4649 // Figure out vertical offset.
4650 CGFloat offset = StatusBarHeight();
4651 if (IsIPadIdiom()) {
4652 offset = MAX(offset, CGRectGetMaxY([_tabStripController view].frame));
4653 }
4654
4655 // Transition steps: Animate the panel position, fade in the toolbar and
4656 // tab strip.
4657 ProceduralBlock transition = ^{
4658 [promotingPanel promoteToMatchSuperviewWithVerticalOffset:offset];
4659 [self updateToolbarControlsAlpha:1.0];
4660 [self updateToolbarBackgroundAlpha:1.0];
4661 [_tabStripController view].alpha = 1.0;
4662 };
4663
4664 // After the transition animation completes, add the tab to the tab model
4665 // (on iPad this triggers the tab strip animation too), then fade out the
4666 // transitioning panel and remove it.
4667 void (^completion)(BOOL) = ^(BOOL finished) {
4668 _contextualSearchMask.alpha = 0;
4669 Tab* newTab = [tabProvider releaseTab];
4670 DCHECK(newTab);
4671 DCHECK([newTab navigationManager]);
4672 // Add the new tab to the tab model.
4673 [newTab setParentTabModel:_model];
4674 // Insert the new tab one after the current tab.
4675 Tab* currentTab = [_model currentTab];
4676 NSUInteger index = [_model indexOfTab:currentTab];
4677 [_model insertTab:newTab atIndex:index + 1];
4678
4679 // Set isPrerenderTab to NO after inserting the tab. This will allow the
4680 // BrowserViewController to detect that a pre-rendered tab is switched in,
4681 // and show the prerendering animation. This needs to happen before the
4682 // tab is made the current tab.
4683 // This also enables contextual search (if otherwise applicable) on
4684 // |newTab|.
4685 newTab.isPrerenderTab = NO;
4686 [_model setCurrentTab:newTab];
4687
sdefresne2f7781c2017-03-02 19:12:464688 if (newTab.loadFinished)
sdefresnee65fd872016-12-19 13:38:134689 [self tabLoadComplete:newTab withSuccess:YES];
4690
4691 if (focusInput) {
4692 [_toolbarController focusOmnibox];
4693 }
4694 _infoBarContainer->RestoreInfobars();
4695
4696 [UIView animateWithDuration:ios::material::kDuration2
4697 animations:^{
4698 promotingPanel.alpha = 0;
4699 }
4700 completion:^(BOOL finished) {
4701 [promotingPanel removeFromSuperview];
4702 }];
4703 };
4704
4705 [UIView animateWithDuration:ios::material::kDuration3
4706 animations:transition
4707 completion:completion];
4708}
4709
4710- (ContextualSearchPanelView*)createPanelView {
4711 PanelConfiguration* config;
4712 CGSize panelContainerSize = self.view.bounds.size;
4713 if (IsIPadIdiom()) {
4714 config = [PadPanelConfiguration
4715 configurationForContainerSize:panelContainerSize
4716 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4717 } else {
4718 config = [PhonePanelConfiguration
4719 configurationForContainerSize:panelContainerSize
4720 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4721 }
4722 ContextualSearchPanelView* newPanel = [[[ContextualSearchPanelView alloc]
4723 initWithConfiguration:config] autorelease];
4724 [newPanel addMotionObserver:self];
4725 [newPanel addMotionObserver:_contextualSearchMask];
4726 return newPanel;
4727}
4728
4729#pragma mark - ContextualSearchPanelMotionObserver
4730
4731- (void)panel:(ContextualSearchPanelView*)panel
4732 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
4733 // If the header is offset, it's offscreen (or moving offscreen) and the
4734 // toolbar shouldn't be opacity-adjusted by the contextual search panel.
4735 if ([self currentHeaderOffset] != 0)
4736 return;
4737
4738 CGFloat toolbarAlpha;
4739
4740 if (motion.state == ContextualSearch::PREVIEWING) {
4741 // As the panel moves past the previewing position, the toolbar should
4742 // become more transparent.
4743 toolbarAlpha = 1 - motion.gradation;
4744 } else if (motion.state == ContextualSearch::COVERING) {
4745 // The toolbar should be totally transparent when the panel is covering.
4746 toolbarAlpha = 0.0;
4747 } else {
4748 return;
4749 }
4750
4751 // On iPad, the toolbar doesn't go fully transparent, so map |toolbarAlpha|'s
4752 // [0-1.0] range to [0.5-1.0].
4753 if (IsIPadIdiom()) {
4754 toolbarAlpha = 0.5 + (toolbarAlpha * 0.5);
4755 [_tabStripController view].alpha = toolbarAlpha;
4756 }
4757
4758 [self updateToolbarControlsAlpha:toolbarAlpha];
4759 [self updateToolbarBackgroundAlpha:toolbarAlpha];
4760}
4761
4762- (void)panel:(ContextualSearchPanelView*)panel
4763 didChangeToState:(ContextualSearch::PanelState)toState
4764 fromState:(ContextualSearch::PanelState)fromState {
4765 if (toState == ContextualSearch::DISMISSED) {
4766 // Panel has become hidden.
4767 _infoBarContainer->RestoreInfobars();
4768 [self updateToolbarControlsAlpha:1.0];
4769 [self updateToolbarBackgroundAlpha:1.0];
4770 [_tabStripController view].alpha = 1.0;
4771 } else if (fromState == ContextualSearch::DISMISSED) {
4772 // Panel has become visible.
4773 _infoBarContainer->SuspendInfobars();
4774 }
4775}
4776
4777- (void)panelWillPromote:(ContextualSearchPanelView*)panel {
4778 [panel removeMotionObserver:self];
4779}
4780
4781- (CGFloat)currentHeaderHeight {
4782 return [self headerHeight] - [self currentHeaderOffset];
4783}
4784
4785#pragma mark - InfoBarControllerDelegate
4786
4787- (void)infoBarContainerStateChanged:(bool)isAnimating {
4788 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4789 DCHECK(infoBarContainerView);
4790 CGRect containerFrame = infoBarContainerView.frame;
4791 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4792 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4793 containerFrame.size.height = height;
4794 BOOL isViewVisible = self.visible;
4795 [UIView animateWithDuration:0.1
4796 animations:^{
4797 [infoBarContainerView setFrame:containerFrame];
4798 }
4799 completion:^(BOOL finished) {
4800 if (!isViewVisible)
4801 return;
4802 UIAccessibilityPostNotification(
4803 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4804 }];
4805}
4806
4807- (BOOL)shouldAutorotate {
4808 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4809 // Don't rotate if a voice search is being presented or dismissed. Once the
4810 // transition animations finish, only the Voice Search UIViewController's
4811 // |-shouldAutorotate| will be called.
4812 return NO;
4813 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4814 // Don't auto rotate if side swipe controller view says not to.
4815 return NO;
4816 } else {
4817 return [super shouldAutorotate];
4818 }
4819}
4820
4821// Always return yes, as this tap should work with various recognizers,
4822// including UITextTapRecognizer, UILongPressGestureRecognizer,
4823// UIScrollViewPanGestureRecognizer and others.
4824- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4825 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4826 (UIGestureRecognizer*)otherGestureRecognizer {
4827 return YES;
4828}
4829
4830// Tap gestures should only be recognized within |_contentArea|.
4831- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4832 CGPoint location = [gesture locationInView:self.view];
4833
4834 // Only allow touches on descendant views of |_contentArea|.
4835 UIView* hitView = [self.view hitTest:location withEvent:nil];
4836 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4837}
4838
4839#pragma mark - SideSwipeController Delegate Methods
4840
4841- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4842 DCHECK(!IsIPadIdiom());
4843 // Update frame incase orientation changed while |_contentArea| was out of
4844 // the view hierarchy.
4845 [_contentArea setFrame:[sideSwipeView frame]];
4846
4847 [self.view insertSubview:_contentArea atIndex:0];
4848 [self updateVoiceSearchBarVisibilityAnimated:NO];
4849 [self updateToolbar];
4850
4851 // Reset horizontal stack view.
4852 [sideSwipeView removeFromSuperview];
4853 [_sideSwipeController setInSwipe:NO];
4854 [_infoBarContainer->view() setHidden:NO];
4855}
4856
4857- (UIView*)contentView {
4858 return _contentArea;
4859}
4860
4861- (TabStripController*)tabStripController {
4862 return _tabStripController;
4863}
4864
4865- (WebToolbarController*)toolbarController {
4866 return _toolbarController;
4867}
4868
4869- (BOOL)preventSideSwipe {
4870 if ([_toolbarController toolsPopupController])
4871 return YES;
4872
4873 if (_voiceSearchController && _voiceSearchController->IsVisible())
4874 return YES;
4875
4876 if ([_contextualSearchPanel state] >= ContextualSearch::PEEKING)
4877 return YES;
4878
4879 if (!self.active)
4880 return YES;
4881
4882 return NO;
4883}
4884
4885- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4886 if (visible) {
4887 [self updateVoiceSearchBarVisibilityAnimated:NO];
4888 [self updateToolbar];
4889 [_infoBarContainer->view() setHidden:NO];
4890 } else {
4891 // Hide UI accessories such as find bar and first visit overlays
4892 // for welcome page.
4893 [self hideFindBarWithAnimation:NO];
4894 [_infoBarContainer->view() setHidden:YES];
4895 [_voiceSearchBar setHidden:YES];
4896 }
4897}
4898
4899- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4900 BOOL seenToolbar = NO;
4901 BOOL seenInfoBarContainer = NO;
4902 BOOL seenContentArea = NO;
4903 for (UIView* view in views.subviews) {
4904 if (view == [_toolbarController view])
4905 seenToolbar = YES;
4906 else if (view == _infoBarContainer->view())
4907 seenInfoBarContainer = YES;
4908 else if (view == _contentArea)
4909 seenContentArea = YES;
4910 if ((seenToolbar && !seenInfoBarContainer) ||
4911 (seenInfoBarContainer && !seenContentArea))
4912 return NO;
4913 }
4914 return YES;
4915}
4916
4917#pragma mark - PreloadControllerDelegate methods
4918
4919- (BOOL)shouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444920 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134921}
4922
sdefresnee65fd872016-12-19 13:38:134923#pragma mark - BookmarkBridgeMethods
4924
4925// If an added or removed bookmark is the same as the current url, update the
4926// toolbar so the star highlight is kept in sync.
4927- (void)bookmarkNodeModified:(const BookmarkNode*)node {
4928 if ([_model currentTab] && node->url() == [_model currentTab].url)
4929 [self updateToolbar];
4930}
4931
4932// If all bookmarks are removed, update the toolbar so the star highlight is
4933// kept in sync.
4934- (void)allBookmarksRemoved {
4935 [self updateToolbar];
4936}
4937
4938#pragma mark - ShareToDelegate methods
4939
4940- (void)shareDidComplete:(ShareTo::ShareResult)shareStatus
jife5fcd332017-03-16 15:14:584941 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:134942 // The shareTo dialog dismisses itself instead of through
4943 // |-dismissViewControllerAnimated:completion:| so we must reset the
4944 // presenting state here.
4945 self.presenting = NO;
4946 [self.dialogPresenter tryToPresent];
4947
4948 switch (shareStatus) {
4949 case ShareTo::SHARE_SUCCESS:
pinkerton07e27842017-03-02 15:29:024950 if ([message length]) {
4951 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:134952 [self showSnackbar:message];
pinkerton07e27842017-03-02 15:29:024953 }
sdefresnee65fd872016-12-19 13:38:134954 break;
4955 case ShareTo::SHARE_ERROR:
4956 [self showErrorAlert:IDS_IOS_SHARE_TO_ERROR_ALERT_TITLE
4957 message:IDS_IOS_SHARE_TO_ERROR_ALERT];
4958 break;
4959 case ShareTo::SHARE_NETWORK_FAILURE:
4960 [self showErrorAlert:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT_TITLE
4961 message:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT];
4962 break;
4963 case ShareTo::SHARE_SIGN_IN_FAILURE:
4964 [self showErrorAlert:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT_TITLE
4965 message:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT];
4966 break;
4967 case ShareTo::SHARE_CANCEL:
4968 case ShareTo::SHARE_UNKNOWN_RESULT:
4969 break;
4970 }
4971}
4972
4973- (void)passwordAppExDidFinish:(ShareTo::ShareResult)shareStatus
4974 username:(NSString*)username
4975 password:(NSString*)password
jife5fcd332017-03-16 15:14:584976 completionMessage:(NSString*)message {
sdefresnee65fd872016-12-19 13:38:134977 switch (shareStatus) {
4978 case ShareTo::SHARE_SUCCESS: {
4979 PasswordController* passwordController =
4980 [[_model currentTab] passwordController];
4981 __block BOOL shown = NO;
4982 [passwordController findAndFillPasswordForms:username
4983 password:password
4984 completionHandler:^(BOOL completed) {
4985 if (shown || !completed || ![message length])
4986 return;
pinkerton07e27842017-03-02 15:29:024987 TriggerHapticFeedbackForNotification(
4988 UINotificationFeedbackTypeSuccess);
sdefresnee65fd872016-12-19 13:38:134989 [self showSnackbar:message];
4990 shown = YES;
4991 }];
4992 break;
4993 }
4994 default:
4995 break;
4996 }
4997}
4998
4999- (void)showErrorAlert:(int)titleMessageId message:(int)messageId {
5000 NSString* title = l10n_util::GetNSString(titleMessageId);
5001 NSString* message = l10n_util::GetNSString(messageId);
5002 [self showErrorAlertWithStringTitle:title message:message];
5003}
5004
5005- (void)showErrorAlertWithStringTitle:(NSString*)title
5006 message:(NSString*)message {
5007 // Dismiss current alert.
5008 [_alertCoordinator stop];
5009
5010 _alertCoordinator.reset(
5011 [[_dependencyFactory alertCoordinatorWithTitle:title
5012 message:message
5013 viewController:self] retain]);
5014 [_alertCoordinator start];
5015}
5016
5017- (void)showSnackbar:(NSString*)message {
5018 [_dependencyFactory showSnackbarWithMessage:message];
5019}
5020
5021#pragma mark - Show Mail Composer methods
5022
5023- (void)showMailComposer:(id)sender {
5024 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
5025 if (![MFMailComposeViewController canSendMail]) {
5026 NSString* alertTitle =
5027 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
5028 NSString* alertMessage =
5029 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
5030 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5031 return;
5032 }
5033 base::scoped_nsobject<MFMailComposeViewController> mailViewController(
5034 [[MFMailComposeViewController alloc] init]);
5035 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
5036 [mailViewController setToRecipients:[command toRecipients]];
5037 [mailViewController setSubject:[command subject]];
5038 [mailViewController setMessageBody:[command body] isHTML:NO];
5039
5040 const base::FilePath& textFile = [command textFileToAttach];
5041 if (!textFile.empty()) {
5042 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5043 NSData* data = [NSData dataWithContentsOfFile:filename];
5044 if (data) {
5045 NSString* displayName =
5046 base::SysUTF8ToNSString(textFile.BaseName().value());
5047 [mailViewController addAttachmentData:data
5048 mimeType:@"text/plain"
5049 fileName:displayName];
5050 }
5051 }
5052
5053 [mailViewController setMailComposeDelegate:self];
5054 [self presentViewController:mailViewController animated:YES completion:nil];
5055}
5056
5057#pragma mark - MFMailComposeViewControllerDelegate methods
5058
5059- (void)mailComposeController:(MFMailComposeViewController*)controller
5060 didFinishWithResult:(MFMailComposeResult)result
5061 error:(NSError*)error {
5062 [self dismissViewControllerAnimated:YES completion:nil];
5063}
5064
5065#pragma mark - StoreKitLauncher methods
5066
5067- (void)productViewControllerDidFinish:
5068 (SKStoreProductViewController*)viewController {
5069 [self dismissViewControllerAnimated:YES completion:nil];
5070}
5071
5072- (void)openAppStore:(NSString*)appId {
5073 if (![appId length])
5074 return;
5075 NSDictionary* product =
5076 @{SKStoreProductParameterITunesItemIdentifier : appId};
5077 base::scoped_nsobject<SKStoreProductViewController> storeViewController(
5078 [[SKStoreProductViewController alloc] init]);
5079 [storeViewController setDelegate:self];
5080 [storeViewController loadProductWithParameters:product completionBlock:nil];
5081 [self presentViewController:storeViewController animated:YES completion:nil];
5082}
5083
5084#pragma mark - TabDialogDelegate methods
5085
sdefresnee65fd872016-12-19 13:38:135086- (void)cancelDialogForTab:(Tab*)tab {
5087 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5088}
5089
5090#pragma mark - FKFeedbackPromptDelegate methods
5091
5092- (void)userTappedRateApp:(UIView*)view {
5093 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
5094 _rateThisAppDialog.reset();
5095}
5096
5097- (void)userTappedSendFeedback:(UIView*)view {
5098 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
5099 _rateThisAppDialog.reset();
5100 base::scoped_nsobject<GenericChromeCommand> command(
5101 [[GenericChromeCommand alloc] initWithTag:IDC_REPORT_AN_ISSUE]);
5102 [self chromeExecuteCommand:command];
5103}
5104
5105- (void)userTappedDismiss:(UIView*)view {
5106 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
5107 _rateThisAppDialog.reset();
5108}
5109
5110#pragma mark - VoiceSearchBarDelegate
5111
5112- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
5113 DCHECK_EQ(_voiceSearchBar.get(), voiceSearchBar);
5114 [self ensureVoiceSearchControllerCreated];
5115 return _voiceSearchController->IsTextToSpeechEnabled() &&
5116 _voiceSearchController->IsTextToSpeechSupported();
5117}
5118
5119- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
5120 DCHECK_EQ(_voiceSearchBar.get(), voiceSearchBar);
5121 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5122}
5123
5124#pragma mark - VoiceSearchPresenter
5125
5126- (UIView*)voiceSearchButton {
5127 return _voiceSearchButton;
5128}
5129
5130- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5131 return [self currentLogoAnimationControllerOwner];
5132}
5133
5134@end