blob: d8e1a0bea234f8ce85567da9de5b48be871f1cca [file] [log] [blame]
// Copyright 2015 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/web/web_state/ui/wk_web_view_configuration_provider.h"
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#include "base/check.h"
#include "base/ios/ios_util.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#include "components/safe_browsing/core/features.h"
#include "ios/web/common/features.h"
#import "ios/web/js_messaging/crw_wk_script_message_router.h"
#import "ios/web/js_messaging/page_script_util.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/web_client.h"
#import "ios/web/web_state/ui/wk_content_rule_list_provider.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider_observer.h"
#import "ios/web/webui/crw_web_ui_scheme_handler.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
namespace {
// A key used to associate a WKWebViewConfigurationProvider with a BrowserState.
const char kWKWebViewConfigProviderKeyName[] = "wk_web_view_config_provider";
// Returns a WKUserScript for JavsScript injected into the main frame at the
// beginning of the document load.
WKUserScript* InternalGetDocumentStartScriptForMainFrame(
BrowserState* browser_state) {
return [[WKUserScript alloc]
initWithSource:GetDocumentStartScriptForMainFrame(browser_state)
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES];
}
// Returns a WKUserScript for JavsScript injected into the main frame at the
// end of the document load.
WKUserScript* InternalGetDocumentEndScriptForMainFrame(
BrowserState* browser_state) {
return [[WKUserScript alloc]
initWithSource:GetDocumentEndScriptForMainFrame(browser_state)
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES];
}
// Returns a WKUserScript for JavsScript injected into all frames at the
// beginning of the document load.
WKUserScript* InternalGetDocumentStartScriptForAllFrames(
BrowserState* browser_state) {
return [[WKUserScript alloc]
initWithSource:GetDocumentStartScriptForAllFrames(browser_state)
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:NO];
}
// Returns a WKUserScript for JavsScript injected into all frames at the
// end of the document load.
WKUserScript* InternalGetDocumentEndScriptForAllFrames(
BrowserState* browser_state) {
return [[WKUserScript alloc]
initWithSource:GetDocumentEndScriptForAllFrames(browser_state)
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:NO];
}
} // namespace
// static
WKWebViewConfigurationProvider&
WKWebViewConfigurationProvider::FromBrowserState(BrowserState* browser_state) {
DCHECK([NSThread isMainThread]);
DCHECK(browser_state);
if (!browser_state->GetUserData(kWKWebViewConfigProviderKeyName)) {
browser_state->SetUserData(
kWKWebViewConfigProviderKeyName,
base::WrapUnique(new WKWebViewConfigurationProvider(browser_state)));
}
return *(static_cast<WKWebViewConfigurationProvider*>(
browser_state->GetUserData(kWKWebViewConfigProviderKeyName)));
}
WKWebViewConfigurationProvider::WKWebViewConfigurationProvider(
BrowserState* browser_state)
: browser_state_(browser_state),
content_rule_list_provider_(
std::make_unique<WKContentRuleListProvider>(browser_state)) {}
WKWebViewConfigurationProvider::~WKWebViewConfigurationProvider() = default;
void WKWebViewConfigurationProvider::ResetWithWebViewConfiguration(
WKWebViewConfiguration* configuration) {
DCHECK([NSThread isMainThread]);
if (!configuration) {
configuration = [[WKWebViewConfiguration alloc] init];
} else {
configuration = [configuration copy];
}
if (configuration_) {
Purge();
}
configuration_ = configuration;
if (browser_state_->IsOffTheRecord()) {
[configuration_
setWebsiteDataStore:[WKWebsiteDataStore nonPersistentDataStore]];
}
[configuration_ setIgnoresViewportScaleLimits:YES];
if (@available(iOS 13, *)) {
@try {
// Disable system context menu on iOS 13 and later. Disabling
// "longPressActions" prevents the WKWebView ContextMenu from being
// displayed and also prevents the iOS 13 ContextMenu delegate methods
// from being called.
// https://github.com/WebKit/webkit/blob/1233effdb7826a5f03b3cdc0f67d713741e70976/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm#L307
BOOL disable_long_press_system_actions =
web::GetWebClient()->EnableLongPressAndForceTouchHandling() ||
web::GetWebClient()->EnableLongPressUIContextMenu();
[configuration_ setValue:@(!disable_long_press_system_actions)
forKey:@"longPressActionsEnabled"];
} @catch (NSException* exception) {
NOTREACHED() << "Error setting value for longPressActionsEnabled";
}
// WKWebView's "fradulentWebsiteWarning" is an iOS 13+ feature that is
// conceptually similar to Safe Browsing but uses a non-Google provider and
// only works for devices in certain locales. Disable this feature since
// Chrome uses Google-provided Safe Browsing.
[[configuration_ preferences] setFraudulentWebsiteWarningEnabled:NO];
}
[configuration_ setAllowsInlineMediaPlayback:YES];
// setJavaScriptCanOpenWindowsAutomatically is required to support popups.
[[configuration_ preferences] setJavaScriptCanOpenWindowsAutomatically:YES];
UpdateScripts();
if (!scheme_handler_) {
scoped_refptr<network::SharedURLLoaderFactory> shared_loader_factory =
browser_state_->GetSharedURLLoaderFactory();
scheme_handler_ = [[CRWWebUISchemeHandler alloc]
initWithURLLoaderFactory:shared_loader_factory];
}
WebClient::Schemes schemes;
GetWebClient()->AddAdditionalSchemes(&schemes);
GetWebClient()->GetAdditionalWebUISchemes(&(schemes.standard_schemes));
for (std::string scheme : schemes.standard_schemes) {
[configuration_ setURLSchemeHandler:scheme_handler_
forURLScheme:base::SysUTF8ToNSString(scheme)];
}
content_rule_list_provider_->SetUserContentController(
configuration_.userContentController);
for (auto& observer : observers_)
observer.DidCreateNewConfiguration(this, configuration_);
// Workaround to force the creation of the WKWebsiteDataStore. This
// workaround need to be done here, because this method returns a copy of
// the already created configuration.
NSSet* data_types = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[configuration_.websiteDataStore
fetchDataRecordsOfTypes:data_types
completionHandler:^(NSArray<WKWebsiteDataRecord*>* records){
}];
}
WKWebViewConfiguration*
WKWebViewConfigurationProvider::GetWebViewConfiguration() {
DCHECK([NSThread isMainThread]);
if (!configuration_) {
ResetWithWebViewConfiguration(nil);
}
// This is a shallow copy to prevent callers from changing the internals of
// configuration.
return [configuration_ copy];
}
CRWWKScriptMessageRouter*
WKWebViewConfigurationProvider::GetScriptMessageRouter() {
DCHECK([NSThread isMainThread]);
if (!router_) {
WKUserContentController* userContentController =
[GetWebViewConfiguration() userContentController];
router_ = [[CRWWKScriptMessageRouter alloc]
initWithUserContentController:userContentController];
}
return router_;
}
WKContentRuleListProvider*
WKWebViewConfigurationProvider::GetContentRuleListProvider() {
return content_rule_list_provider_.get();
}
void WKWebViewConfigurationProvider::UpdateScripts() {
[configuration_.userContentController removeAllUserScripts];
// Main frame script depends upon scripts injected into all frames, so the
// "AllFrames" scripts must be injected first.
[configuration_.userContentController
addUserScript:InternalGetDocumentStartScriptForAllFrames(browser_state_)];
[configuration_.userContentController
addUserScript:InternalGetDocumentStartScriptForMainFrame(browser_state_)];
[configuration_.userContentController
addUserScript:InternalGetDocumentEndScriptForAllFrames(browser_state_)];
[configuration_.userContentController
addUserScript:InternalGetDocumentEndScriptForMainFrame(browser_state_)];
}
void WKWebViewConfigurationProvider::Purge() {
DCHECK([NSThread isMainThread]);
configuration_ = nil;
router_ = nil;
}
void WKWebViewConfigurationProvider::AddObserver(
WKWebViewConfigurationProviderObserver* observer) {
observers_.AddObserver(observer);
}
void WKWebViewConfigurationProvider::RemoveObserver(
WKWebViewConfigurationProviderObserver* observer) {
observers_.RemoveObserver(observer);
}
} // namespace web