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