blob: db88974a9eaf282b94dbc350d3c0a097c45b076f [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/tabs/tab.h"
#import <CoreLocation/CoreLocation.h>
#import <UIKit/UIKit.h>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/ios/block_types.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/scoped_observer.h"
#include "base/strings/string_split.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/google/core/common/google_util.h"
#include "components/history/core/browser/history_context.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/top_sites.h"
#include "components/history/ios/browser/web_state_top_sites_observer.h"
#include "components/infobars/core/infobar_manager.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/prefs/pref_service.h"
#include "components/reading_list/core/reading_list_model.h"
#include "components/search_engines/template_url_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/url_formatter.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/download/download_manager_tab_helper.h"
#include "ios/chrome/browser/experimental_flags.h"
#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
#include "ios/chrome/browser/history/history_service_factory.h"
#include "ios/chrome/browser/history/history_tab_helper.h"
#include "ios/chrome/browser/history/top_sites_factory.h"
#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
#import "ios/chrome/browser/metrics/tab_usage_recorder.h"
#include "ios/chrome/browser/pref_names.h"
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_cache.h"
#import "ios/chrome/browser/snapshots/snapshot_cache_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
#import "ios/chrome/browser/tabs/tab_helper_util.h"
#import "ios/chrome/browser/tabs/tab_private.h"
#include "ios/chrome/browser/translate/chrome_ios_translate_client.h"
#import "ios/chrome/browser/u2f/u2f_controller.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/commands/show_signin_command.h"
#import "ios/chrome/browser/ui/open_in_controller.h"
#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/web/navigation/navigation_manager_impl.h"
#include "ios/web/public/favicon_status.h"
#include "ios/web/public/favicon_url.h"
#include "ios/web/public/interstitials/web_interstitial.h"
#include "ios/web/public/load_committed_details.h"
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#include "ios/web/public/referrer.h"
#import "ios/web/public/serializable_user_data_manager.h"
#include "ios/web/public/ssl_status.h"
#include "ios/web/public/url_scheme_util.h"
#include "ios/web/public/url_util.h"
#include "ios/web/public/web_client.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#import "ios/web/public/web_state/navigation_context.h"
#import "ios/web/public/web_state/ui/crw_generic_content_view.h"
#import "ios/web/public/web_state/web_state.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
#include "ios/web/public/web_thread.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#include "net/base/escape.h"
#include "net/base/filename_util.h"
#import "net/base/mac/url_conversions.h"
#include "net/base/net_errors.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_fetcher.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/page_transition_types.h"
#include "url/origin.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
NSString* const kTabUrlStartedLoadingNotificationForCrashReporting =
@"kTabUrlStartedLoadingNotificationForCrashReporting";
NSString* const kTabUrlMayStartLoadingNotificationForCrashReporting =
@"kTabUrlMayStartLoadingNotificationForCrashReporting";
NSString* const kTabIsShowingExportableNotificationForCrashReporting =
@"kTabIsShowingExportableNotificationForCrashReporting";
NSString* const kTabClosingCurrentDocumentNotificationForCrashReporting =
@"kTabClosingCurrentDocumentNotificationForCrashReporting";
NSString* const kTabUrlKey = @"url";
@interface Tab ()<CRWWebStateObserver> {
ios::ChromeBrowserState* _browserState;
OpenInController* _openInController;
// The Overscroll controller responsible for displaying the
// overscrollActionsView above the toolbar.
OverscrollActionsController* _overscrollActionsController;
// WebStateImpl for this tab.
web::WebStateImpl* _webStateImpl;
// Allows Tab to conform CRWWebStateDelegate protocol.
std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
// Universal Second Factor (U2F) call controller.
U2FController* _secondFactorController;
}
// Returns the OpenInController for this tab.
- (OpenInController*)openInController;
// Handles exportable files if possible.
- (void)handleExportableFile:(net::HttpResponseHeaders*)headers;
@end
@implementation Tab
@synthesize browserState = _browserState;
@synthesize overscrollActionsController = _overscrollActionsController;
@synthesize overscrollActionsControllerDelegate =
overscrollActionsControllerDelegate_;
@synthesize dialogDelegate = dialogDelegate_;
#pragma mark - Initializers
- (instancetype)initWithWebState:(web::WebState*)webState {
DCHECK(webState);
self = [super init];
if (self) {
// TODO(crbug.com/620465): Tab should only use public API of WebState.
// Remove this cast once this is the case.
_webStateImpl = static_cast<web::WebStateImpl*>(webState);
_webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
_webStateImpl->AddObserver(_webStateObserver.get());
_browserState =
ios::ChromeBrowserState::FromBrowserState(webState->GetBrowserState());
}
return self;
}
#pragma mark - NSObject protocol
- (void)dealloc {
// The WebState owns the Tab, so -webStateDestroyed: should be called before
// -dealloc and _webStateImpl set to nullptr.
DCHECK(!_webStateImpl);
}
- (NSString*)description {
return
[NSString stringWithFormat:@"%p ... - %s", self,
self.webState->GetVisibleURL().spec().c_str()];
}
#pragma mark - Properties
- (NSString*)tabId {
if (!self.webState) {
// Tab can outlive WebState, in which case Tab is not valid anymore and
// tabId should be nil.
return nil;
}
TabIdTabHelper* tab_id_helper = TabIdTabHelper::FromWebState(self.webState);
DCHECK(tab_id_helper);
return tab_id_helper->tab_id();
}
- (web::WebState*)webState {
return _webStateImpl;
}
- (BOOL)canGoBack {
return self.navigationManager && self.navigationManager->CanGoBack();
}
- (BOOL)canGoForward {
return self.navigationManager && self.navigationManager->CanGoForward();
}
- (void)setOverscrollActionsControllerDelegate:
(id<OverscrollActionsControllerDelegate>)
overscrollActionsControllerDelegate {
if (overscrollActionsControllerDelegate_ ==
overscrollActionsControllerDelegate) {
return;
}
// Lazily create a OverscrollActionsController.
// The check for overscrollActionsControllerDelegate is necessary to avoid
// recreating a OverscrollActionsController during teardown.
if (!_overscrollActionsController) {
_overscrollActionsController = [[OverscrollActionsController alloc]
initWithWebViewProxy:self.webState->GetWebViewProxy()];
}
OverscrollStyle style = OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
if (_browserState->IsOffTheRecord())
style = OverscrollStyle::REGULAR_PAGE_INCOGNITO;
[_overscrollActionsController setStyle:style];
[_overscrollActionsController
setDelegate:overscrollActionsControllerDelegate];
[_overscrollActionsController setBrowserState:self.browserState];
overscrollActionsControllerDelegate_ = overscrollActionsControllerDelegate;
}
#pragma mark - Public API
- (UIView*)viewForPrinting {
return self.webController.viewForPrinting;
}
// Halt the tab, which amounts to halting its webController.
- (void)terminateNetworkActivity {
[self.webController terminateNetworkActivity];
}
- (void)dismissModals {
[_openInController disable];
[self.webController dismissModals];
}
- (web::NavigationManager*)navigationManager {
return self.webState ? self.webState->GetNavigationManager() : nullptr;
}
- (void)goBack {
if (self.navigationManager) {
DCHECK(self.navigationManager->CanGoBack());
base::RecordAction(base::UserMetricsAction("Back"));
self.navigationManager->GoBack();
}
}
- (void)goForward {
if (self.navigationManager) {
DCHECK(self.navigationManager->CanGoForward());
base::RecordAction(base::UserMetricsAction("Forward"));
self.navigationManager->GoForward();
}
}
- (void)willUpdateSnapshot {
[_overscrollActionsController clear];
}
- (void)notifyTabOfUrlMayStartLoading:(const GURL&)url {
NSString* urlString = base::SysUTF8ToNSString(url.spec());
if ([urlString length]) {
[[NSNotificationCenter defaultCenter]
postNotificationName:kTabUrlMayStartLoadingNotificationForCrashReporting
object:self
userInfo:@{kTabUrlKey : urlString}];
}
}
#pragma mark - Public API (relating to U2F)
- (void)evaluateU2FResultFromURL:(const GURL&)URL {
DCHECK(_secondFactorController);
[_secondFactorController evaluateU2FResultFromU2FURL:URL
webState:self.webState];
}
- (GURL)XCallbackFromRequestURL:(const GURL&)requestURL
originURL:(const GURL&)originURL {
// Create U2FController object lazily.
if (!_secondFactorController)
_secondFactorController = [[U2FController alloc] init];
return [_secondFactorController
XCallbackFromRequestURL:requestURL
originURL:originURL
tabURL:self.webState->GetLastCommittedURL()
tabID:self.tabId];
}
#pragma mark - CRWWebStateObserver protocol
- (void)webState:(web::WebState*)webState
didStartNavigation:(web::NavigationContext*)navigation {
// Notify tab of Url may start loading, this notification is not sent in cases
// of app launching, history api navigations, and hash change navigations.
[self notifyTabOfUrlMayStartLoading:navigation->GetUrl()];
[self.dialogDelegate cancelDialogForTab:self];
[_openInController disable];
}
- (void)webState:(web::WebState*)webState
didLoadPageWithSuccess:(BOOL)loadSuccess {
if (loadSuccess) {
scoped_refptr<net::HttpResponseHeaders> headers =
_webStateImpl->GetHttpResponseHeaders();
[self handleExportableFile:headers.get()];
}
}
- (void)renderProcessGoneForWebState:(web::WebState*)webState {
DCHECK(webState == _webStateImpl);
[self.dialogDelegate cancelDialogForTab:self];
}
- (void)webStateDestroyed:(web::WebState*)webState {
DCHECK_EQ(_webStateImpl, webState);
self.overscrollActionsControllerDelegate = nil;
[_openInController detachFromWebController];
_openInController = nil;
[_overscrollActionsController invalidate];
_overscrollActionsController = nil;
// Cancel any queued dialogs.
[self.dialogDelegate cancelDialogForTab:self];
_webStateImpl->RemoveObserver(_webStateObserver.get());
_webStateObserver.reset();
_webStateImpl = nullptr;
}
#pragma mark - Private methods
- (OpenInController*)openInController {
if (!_openInController) {
_openInController = [[OpenInController alloc]
initWithURLLoaderFactory:_browserState->GetSharedURLLoaderFactory()
webController:self.webController];
// Previously evicted tabs should be reloaded before this method is called.
DCHECK(!self.webState->IsEvicted());
self.webState->GetNavigationManager()->LoadIfNecessary();
_openInController.baseView = self.webState->GetView();
}
return _openInController;
}
- (void)handleExportableFile:(net::HttpResponseHeaders*)headers {
// Only "application/pdf" is supported for now.
if (self.webState->GetContentsMimeType() != "application/pdf")
return;
[[NSNotificationCenter defaultCenter]
postNotificationName:kTabIsShowingExportableNotificationForCrashReporting
object:self];
// Try to generate a filename by first looking at |content_disposition_|, then
// at the last component of WebState's last committed URL and if both of these
// fail use the default filename "document".
std::string contentDisposition;
if (headers)
headers->GetNormalizedHeader("content-disposition", &contentDisposition);
std::string defaultFilename =
l10n_util::GetStringUTF8(IDS_IOS_OPEN_IN_FILE_DEFAULT_TITLE);
const GURL& lastCommittedURL = self.webState->GetLastCommittedURL();
base::string16 filename =
net::GetSuggestedFilename(lastCommittedURL, contentDisposition,
"", // referrer-charset
"", // suggested-name
"application/pdf", // mime-type
defaultFilename);
[[self openInController]
enableWithDocumentURL:lastCommittedURL
suggestedFilename:base::SysUTF16ToNSString(filename)];
}
@end
#pragma mark - TestingSupport
@implementation Tab (TestingSupport)
// TODO(crbug.com/620465): this require the Tab's WebState to be a WebStateImpl,
// remove this helper once this is no longer true (and fix the unit tests).
- (CRWWebController*)webController {
return _webStateImpl ? _webStateImpl->GetWebController() : nil;
}
@end