blob: 97acf5eb39605692edc601496e0747a97b3c6559 [file] [log] [blame]
// Copyright 2014 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.
#include "ios/chrome/browser/web/chrome_web_client.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#import "base/ios/ios_util.h"
#import "base/ios/ns_error_util.h"
#include "base/mac/bundle_locations.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/version.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/suggestion_controller_java_script_feature.h"
#import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/dom_distiller/core/url_constants.h"
#include "components/google/core/common/google_util.h"
#include "components/password_manager/core/common/password_manager_features.h"
#import "components/password_manager/ios/password_manager_java_script_feature.h"
#include "components/strings/grit/components_strings.h"
#include "components/version_info/version_info.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_about_rewriter.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_switches.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "ios/chrome/browser/ios_chrome_main_parts.h"
#import "ios/chrome/browser/link_to_text/link_to_text_java_script_feature.h"
#import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/reading_list/offline_page_tab_helper.h"
#include "ios/chrome/browser/reading_list/offline_url_utils.h"
#import "ios/chrome/browser/safe_browsing/password_protection_java_script_feature.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_error.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
#import "ios/chrome/browser/search_engines/search_engine_java_script_feature.h"
#import "ios/chrome/browser/search_engines/search_engine_tab_helper_factory.h"
#include "ios/chrome/browser/ssl/ios_ssl_error_handler.h"
#import "ios/chrome/browser/ui/elements/windowed_container_view.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_features.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_javascript_feature.h"
#include "ios/chrome/browser/ui/ui_feature_flags.h"
#include "ios/chrome/browser/web/error_page_controller_bridge.h"
#import "ios/chrome/browser/web/error_page_util.h"
#include "ios/chrome/browser/web/features.h"
#include "ios/chrome/browser/web/image_fetch/image_fetch_java_script_feature.h"
#import "ios/chrome/browser/web/java_script_console/java_script_console_feature.h"
#import "ios/chrome/browser/web/java_script_console/java_script_console_feature_factory.h"
#include "ios/chrome/browser/web/print/print_java_script_feature.h"
#import "ios/chrome/browser/web/session_state/web_session_state_tab_helper.h"
#import "ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_controller_client.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h"
#include "ios/components/webui/web_ui_url_constants.h"
#import "ios/net/protocol_handler_util.h"
#import "ios/public/provider/chrome/browser/font_size_java_script_feature.h"
#include "ios/public/provider/chrome/browser/url_rewriters/url_rewriters_api.h"
#include "ios/web/common/features.h"
#include "ios/web/common/user_agent.h"
#include "ios/web/public/navigation/browser_url_rewriter.h"
#import "ios/web/public/navigation/navigation_item.h"
#include "ios/web/public/navigation/navigation_manager.h"
#include "net/base/net_errors.h"
#include "net/http/http_util.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// The tag describing the product name with a placeholder for the version.
const char kProductTagWithPlaceholder[] = "CriOS/%s";
constexpr char kMajorVersion100[] = "100";
// Returns an autoreleased string containing the JavaScript loaded from a
// bundled resource file with the given name (excluding extension).
NSString* GetPageScript(NSString* script_file_name) {
DCHECK(script_file_name);
NSString* path =
[base::mac::FrameworkBundle() pathForResource:script_file_name
ofType:@"js"];
DCHECK(path) << "Script file not found: "
<< base::SysNSStringToUTF8(script_file_name) << ".js";
NSError* error = nil;
NSString* content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:&error];
DCHECK(!error) << "Error fetching script: "
<< base::SysNSStringToUTF8(error.description);
DCHECK(content);
return content;
}
// Returns the safe browsing error page HTML.
NSString* GetSafeBrowsingErrorPageHTML(web::WebState* web_state,
int64_t navigation_id) {
// Fetch the unsafe resource causing this error page from the WebState's
// container.
SafeBrowsingUnsafeResourceContainer* container =
SafeBrowsingUnsafeResourceContainer::FromWebState(web_state);
const security_interstitials::UnsafeResource* resource =
container->GetMainFrameUnsafeResource()
?: container->GetSubFrameUnsafeResource(
web_state->GetNavigationManager()->GetLastCommittedItem());
// Construct the blocking page and associate it with the WebState.
std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
SafeBrowsingBlockingPage::Create(*resource);
std::string error_page_content = page->GetHtmlContents();
security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
->AssociateBlockingPage(navigation_id, std::move(page));
return base::SysUTF8ToNSString(error_page_content);
}
// Returns the lookalike error page HTML.
NSString* GetLookalikeUrlErrorPageHtml(web::WebState* web_state,
int64_t navigation_id) {
// Fetch the lookalike URL info from the WebState's container.
LookalikeUrlContainer* container =
LookalikeUrlContainer::FromWebState(web_state);
std::unique_ptr<LookalikeUrlContainer::LookalikeUrlInfo> lookalike_info =
container->ReleaseLookalikeUrlInfo();
// Construct the blocking page and associate it with the WebState.
std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
std::make_unique<LookalikeUrlBlockingPage>(
web_state, lookalike_info->safe_url, lookalike_info->request_url,
ukm::ConvertToSourceId(navigation_id,
ukm::SourceIdType::NAVIGATION_ID),
lookalike_info->match_type,
std::make_unique<LookalikeUrlControllerClient>(
web_state, lookalike_info->safe_url, lookalike_info->request_url,
GetApplicationContext()->GetApplicationLocale()));
std::string error_page_content = page->GetHtmlContents();
security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
->AssociateBlockingPage(navigation_id, std::move(page));
return base::SysUTF8ToNSString(error_page_content);
}
// Returns a version string that matches the current version number except that
// the major version is 100.
const std::string& GetM100VersionNumber() {
static const base::NoDestructor<std::string> m100_version_number([] {
base::Version version(version_info::GetVersionNumber());
std::string version_str(kMajorVersion100);
const std::vector<uint32_t>& components = version.components();
// Rest of the version string remains the same.
for (size_t i = 1; i < components.size(); ++i) {
version_str.append(".");
version_str.append(base::NumberToString(components[i]));
}
return version_str;
}());
return *m100_version_number;
}
// Returns a string describing the product name and version, of the
// form "productname/version". Used as part of the user agent string.
std::string GetMobileProduct() {
if (base::FeatureList::IsEnabled(web::kForceMajorVersion100InUserAgent)) {
return base::StringPrintf(kProductTagWithPlaceholder,
GetM100VersionNumber().c_str());
}
return base::StringPrintf(kProductTagWithPlaceholder,
version_info::GetVersionNumber().c_str());
}
// Returns a string describing the product name and version, of the
// form "productname/version". Used as part of the user agent string.
// The Desktop UserAgent is only using the major version to reduce the surface
// for fingerprinting. The Mobile one is using the full version for legacy
// reasons.
std::string GetDesktopProduct() {
if (base::FeatureList::IsEnabled(web::kForceMajorVersion100InUserAgent)) {
return base::StringPrintf(kProductTagWithPlaceholder, kMajorVersion100);
}
return base::StringPrintf(kProductTagWithPlaceholder,
version_info::GetMajorVersionNumber().c_str());
}
} // namespace
ChromeWebClient::ChromeWebClient() {}
ChromeWebClient::~ChromeWebClient() {}
std::unique_ptr<web::WebMainParts> ChromeWebClient::CreateWebMainParts() {
return std::make_unique<IOSChromeMainParts>(
*base::CommandLine::ForCurrentProcess());
}
void ChromeWebClient::PreWebViewCreation() const {}
void ChromeWebClient::AddAdditionalSchemes(Schemes* schemes) const {
schemes->standard_schemes.push_back(kChromeUIScheme);
schemes->secure_schemes.push_back(kChromeUIScheme);
}
std::string ChromeWebClient::GetApplicationLocale() const {
DCHECK(GetApplicationContext());
return GetApplicationContext()->GetApplicationLocale();
}
bool ChromeWebClient::IsAppSpecificURL(const GURL& url) const {
return url.SchemeIs(kChromeUIScheme);
}
std::u16string ChromeWebClient::GetPluginNotSupportedText() const {
return l10n_util::GetStringUTF16(IDS_PLUGIN_NOT_SUPPORTED);
}
std::string ChromeWebClient::GetUserAgent(web::UserAgentType type) const {
// The user agent should not be requested for app-specific URLs.
DCHECK_NE(type, web::UserAgentType::NONE);
// Using desktop user agent overrides a command-line user agent, so that
// request desktop site can still work when using an overridden UA.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (type != web::UserAgentType::DESKTOP &&
command_line->HasSwitch(switches::kUserAgent)) {
std::string user_agent =
command_line->GetSwitchValueASCII(switches::kUserAgent);
if (net::HttpUtil::IsValidHeaderValue(user_agent))
return user_agent;
LOG(WARNING) << "Ignored invalid value for flag --" << switches::kUserAgent;
}
if (type == web::UserAgentType::DESKTOP)
return web::BuildDesktopUserAgent(GetDesktopProduct());
return web::BuildMobileUserAgent(GetMobileProduct());
}
std::u16string ChromeWebClient::GetLocalizedString(int message_id) const {
return l10n_util::GetStringUTF16(message_id);
}
base::StringPiece ChromeWebClient::GetDataResource(
int resource_id,
ui::ResourceScaleFactor scale_factor) const {
return ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
resource_id, scale_factor);
}
base::RefCountedMemory* ChromeWebClient::GetDataResourceBytes(
int resource_id) const {
return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
resource_id);
}
void ChromeWebClient::GetAdditionalWebUISchemes(
std::vector<std::string>* additional_schemes) {
additional_schemes->push_back(dom_distiller::kDomDistillerScheme);
}
void ChromeWebClient::PostBrowserURLRewriterCreation(
web::BrowserURLRewriter* rewriter) {
rewriter->AddURLRewriter(&WillHandleWebBrowserAboutURL);
ios::provider::AddURLRewriters(rewriter);
}
std::vector<web::JavaScriptFeature*> ChromeWebClient::GetJavaScriptFeatures(
web::BrowserState* browser_state) const {
static base::NoDestructor<PrintJavaScriptFeature> print_feature;
std::vector<web::JavaScriptFeature*> features;
if (base::FeatureList::IsEnabled(
password_manager::features::kPasswordReuseDetectionEnabled)) {
features.push_back(PasswordProtectionJavaScriptFeature::GetInstance());
}
JavaScriptConsoleFeature* java_script_console_feature =
JavaScriptConsoleFeatureFactory::GetInstance()->GetForBrowserState(
browser_state);
features.push_back(java_script_console_feature);
features.push_back(print_feature.get());
BOOL shouldInjectReadingListJavaScript =
IsReadingListMessagesEnabled() || IsReadingListTimeToReadEnabled();
if (!browser_state->IsOffTheRecord() && shouldInjectReadingListJavaScript) {
static base::NoDestructor<ReadingListJavaScriptFeature>
reading_list_feature;
features.push_back(reading_list_feature.get());
}
features.push_back(autofill::AutofillJavaScriptFeature::GetInstance());
features.push_back(autofill::FormHandlersJavaScriptFeature::GetInstance());
features.push_back(
autofill::SuggestionControllerJavaScriptFeature::GetInstance());
features.push_back(FontSizeJavaScriptFeature::GetInstance());
features.push_back(ImageFetchJavaScriptFeature::GetInstance());
features.push_back(
password_manager::PasswordManagerJavaScriptFeature::GetInstance());
features.push_back(LinkToTextJavaScriptFeature::GetInstance());
SearchEngineJavaScriptFeature::GetInstance()->SetDelegate(
SearchEngineTabHelperFactory::GetInstance());
features.push_back(SearchEngineJavaScriptFeature::GetInstance());
features.push_back(WebPerformanceMetricsJavaScriptFeature::GetInstance());
return features;
}
NSString* ChromeWebClient::GetDocumentStartScriptForMainFrame(
web::BrowserState* browser_state) const {
NSMutableArray* scripts = [NSMutableArray array];
[scripts addObject:GetPageScript(@"chrome_bundle_main_frame")];
return [scripts componentsJoinedByString:@";"];
}
void ChromeWebClient::PrepareErrorPage(
web::WebState* web_state,
const GURL& url,
NSError* error,
bool is_post,
bool is_off_the_record,
const absl::optional<net::SSLInfo>& info,
int64_t navigation_id,
base::OnceCallback<void(NSString*)> callback) {
OfflinePageTabHelper* offline_page_tab_helper =
OfflinePageTabHelper::FromWebState(web_state);
// WebState that are not attached to a tab may not have an
// OfflinePageTabHelper.
if (offline_page_tab_helper &&
(offline_page_tab_helper->CanHandleErrorLoadingURL(url))) {
// An offline version of the page will be displayed to replace this error
// page. Loading an error page here can cause a race between the
// navigation to load the error page and the navigation to display the
// offline version of the page. If the latter navigation interrupts the
// former and causes it to fail, this can incorrectly appear to be a
// navigation back to the previous committed URL. To avoid this race,
// return a nil error page here to avoid an error page load. See
// crbug.com/980912.
std::move(callback).Run(nil);
return;
}
DCHECK(error);
__block NSString* error_html = nil;
__block base::OnceCallback<void(NSString*)> error_html_callback =
std::move(callback);
NSError* final_underlying_error =
base::ios::GetFinalUnderlyingErrorFromError(error);
if ([final_underlying_error.domain isEqual:kSafeBrowsingErrorDomain]) {
// Only kUnsafeResourceErrorCode is supported.
DCHECK_EQ(kUnsafeResourceErrorCode, final_underlying_error.code);
std::move(error_html_callback)
.Run(GetSafeBrowsingErrorPageHTML(web_state, navigation_id));
} else if ([final_underlying_error.domain isEqual:kLookalikeUrlErrorDomain]) {
// Only kLookalikeUrlErrorCode is supported.
DCHECK_EQ(kLookalikeUrlErrorCode, final_underlying_error.code);
std::move(error_html_callback)
.Run(GetLookalikeUrlErrorPageHtml(web_state, navigation_id));
} else if (info.has_value()) {
base::OnceCallback<void(NSString*)> blocking_page_callback =
base::BindOnce(^(NSString* blocking_page_html) {
error_html = blocking_page_html;
std::move(error_html_callback).Run(error_html);
});
IOSSSLErrorHandler::HandleSSLError(
web_state, net::MapCertStatusToNetError(info.value().cert_status),
info.value(), url, info.value().is_fatal_cert_error, navigation_id,
std::move(blocking_page_callback));
} else {
std::move(error_html_callback)
.Run(GetErrorPage(url, error, is_post, is_off_the_record));
ErrorPageControllerBridge* error_page_controller =
ErrorPageControllerBridge::FromWebState(web_state);
if (error_page_controller) {
// ErrorPageControllerBridge may not be created for web_state not attached
// to a tab.
error_page_controller->StartHandlingJavascriptCommands();
}
}
}
UIView* ChromeWebClient::GetWindowedContainer() {
if (!windowed_container_) {
windowed_container_ = [[WindowedContainerView alloc] init];
}
return windowed_container_;
}
bool ChromeWebClient::EnableLongPressAndForceTouchHandling() const {
return !web::features::UseWebViewNativeContextMenuWeb();
}
bool ChromeWebClient::EnableLongPressUIContextMenu() const {
return true;
}
web::UserAgentType ChromeWebClient::GetDefaultUserAgent(
web::WebState* web_state,
const GURL& url) {
ChromeBrowserState* browser_state =
ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
HostContentSettingsMap* settings_map =
ios::HostContentSettingsMapFactory::GetForBrowserState(browser_state);
ContentSetting setting = settings_map->GetContentSetting(
url, url, ContentSettingsType::REQUEST_DESKTOP_SITE);
return (setting == CONTENT_SETTING_ALLOW) ? web::UserAgentType::DESKTOP
: web::UserAgentType::MOBILE;
}
bool ChromeWebClient::RestoreSessionFromCache(web::WebState* web_state) const {
return WebSessionStateTabHelper::FromWebState(web_state)
->RestoreSessionFromCache();
}
void ChromeWebClient::CleanupNativeRestoreURLs(web::WebState* web_state) const {
web::NavigationManager* navigationManager = web_state->GetNavigationManager();
for (int i = 0; i < navigationManager->GetItemCount(); i++) {
// The WKWebView URL underneath the NTP is about://newtab, which has no
// title. When restoring the NTP, be sure to re-add the title below.
web::NavigationItem* item = navigationManager->GetItemAtIndex(i);
NewTabPageTabHelper::UpdateItem(item);
// The WKWebView URL underneath a forced-offline page is chrome://offline,
// which has an embedded entry URL. Apply that entryURL to the virtualURL
// here.
if (item->GetVirtualURL().host() == kChromeUIOfflineHost) {
item->SetVirtualURL(
reading_list::EntryURLForOfflineURL(item->GetVirtualURL()));
}
}
}