blob: 44c01aed05bb5000f20eaad1cd6617e762d28adc [file] [log] [blame]
// Copyright 2020 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/js_messaging/java_script_content_world.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/optional.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/web/js_messaging/web_view_js_utils.h"
#import "ios/web/js_messaging/web_view_web_state_map.h"
#import "ios/web/public/browser_state.h"
#include "ios/web/public/js_messaging/java_script_feature.h"
#include "ios/web/public/js_messaging/script_message.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
#import "ios/web/web_state/web_state_impl.h"
#import "net/base/mac/url_conversions.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
namespace {
WKUserScriptInjectionTime InjectionTimeToWKUserScriptInjectionTime(
JavaScriptFeature::FeatureScript::InjectionTime injection_time) {
switch (injection_time) {
case JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart:
return WKUserScriptInjectionTimeAtDocumentStart;
case JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd:
return WKUserScriptInjectionTimeAtDocumentEnd;
}
NOTREACHED();
return WKUserScriptInjectionTimeAtDocumentStart;
}
// Returns the WKUserContentController associated with |browser_state|.
// NOTE: Only fetch the WKUserContentController once at construction. Although
// it is not guaranteed to remain constant over the lifetime of the
// application, the entire JavaScriptcontentWorld will be recreated when it
// changes. Calling WKWebViewConfigurationProvider::GetWebViewConfiguration on
// a configuration provider during destruction will cause partial
// re-initialization during tear down.
WKUserContentController* GetUserContentController(BrowserState* browser_state) {
return WKWebViewConfigurationProvider::FromBrowserState(browser_state)
.GetWebViewConfiguration()
.userContentController;
}
} // namespace
JavaScriptContentWorld::JavaScriptContentWorld(BrowserState* browser_state)
: browser_state_(browser_state),
user_content_controller_(GetUserContentController(browser_state)),
weak_factory_(this) {}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
JavaScriptContentWorld::JavaScriptContentWorld(BrowserState* browser_state,
WKContentWorld* content_world)
: browser_state_(browser_state),
user_content_controller_(GetUserContentController(browser_state)),
content_world_(content_world),
weak_factory_(this) {}
WKContentWorld* JavaScriptContentWorld::GetWKContentWorld()
API_AVAILABLE(ios(14.0)) {
return content_world_;
}
#endif // defined(__IPHONE14_0)
JavaScriptContentWorld::~JavaScriptContentWorld() {}
bool JavaScriptContentWorld::HasFeature(const JavaScriptFeature* feature) {
return features_.find(feature) != features_.end();
}
void JavaScriptContentWorld::AddFeature(const JavaScriptFeature* feature) {
if (HasFeature(feature)) {
// |feature| has already been added to this content world.
return;
}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
if (@available(iOS 14, *)) {
// Ensure |feature| supports this content world.
if (content_world_) {
JavaScriptFeature::ContentWorld incompatible_world_value;
if (content_world_ == WKContentWorld.pageWorld) {
// A feature specifying kIsolatedWorldOnly must not be added to the page
// content world.
incompatible_world_value =
JavaScriptFeature::ContentWorld::kIsolatedWorldOnly;
} else {
// A feature specifying kPageContentWorld must not be added to an
// isolated world.
incompatible_world_value =
JavaScriptFeature::ContentWorld::kPageContentWorld;
}
DCHECK_NE(feature->GetSupportedContentWorld(), incompatible_world_value);
}
}
#endif // defined(__IPHONE14_0)
features_.insert(feature);
// Add dependent features first.
for (const JavaScriptFeature* dep_feature : feature->GetDependentFeatures()) {
AddFeature(dep_feature);
}
// Setup user scripts.
for (const JavaScriptFeature::FeatureScript& feature_script :
feature->GetScripts()) {
WKUserScriptInjectionTime injection_time =
InjectionTimeToWKUserScriptInjectionTime(
feature_script.GetInjectionTime());
bool main_frame_only =
feature_script.GetTargetFrames() !=
JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
WKUserScript* user_script = nil;
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
if (@available(iOS 14, *)) {
if (content_world_) {
user_script = [[WKUserScript alloc]
initWithSource:feature_script.GetScriptString()
injectionTime:injection_time
forMainFrameOnly:main_frame_only
inContentWorld:content_world_];
}
}
#endif // defined(__IPHONE14_0)
if (!user_script) {
user_script =
[[WKUserScript alloc] initWithSource:feature_script.GetScriptString()
injectionTime:injection_time
forMainFrameOnly:main_frame_only];
}
[user_content_controller_ addUserScript:user_script];
}
// Setup Javascript message callback.
auto optional_handler_name = feature->GetScriptMessageHandlerName();
if (optional_handler_name) {
auto handler = feature->GetScriptMessageHandler();
DCHECK(handler);
NSString* handler_name =
base::SysUTF8ToNSString(optional_handler_name.value());
std::unique_ptr<ScopedWKScriptMessageHandler> script_message_handler;
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
if (@available(iOS 14, *)) {
if (content_world_) {
script_message_handler = std::make_unique<ScopedWKScriptMessageHandler>(
user_content_controller_, handler_name, content_world_,
base::BindRepeating(&JavaScriptContentWorld::ScriptMessageReceived,
weak_factory_.GetWeakPtr(), handler.value(),
browser_state_));
}
}
#endif // defined(__IPHONE14_0)
if (!script_message_handler.get()) {
script_message_handler = std::make_unique<ScopedWKScriptMessageHandler>(
user_content_controller_, handler_name,
base::BindRepeating(&JavaScriptContentWorld::ScriptMessageReceived,
weak_factory_.GetWeakPtr(), handler.value(),
browser_state_));
}
script_message_handlers_[feature] = std::move(script_message_handler);
}
}
void JavaScriptContentWorld::ScriptMessageReceived(
JavaScriptFeature::ScriptMessageHandler handler,
BrowserState* browser_state,
WKScriptMessage* script_message) {
web::WebViewWebStateMap* map =
web::WebViewWebStateMap::FromBrowserState(browser_state);
web::WebState* web_state = map->GetWebStateForWebView(script_message.webView);
// Drop messages if they are no longer associated with a WebState.
if (!web_state) {
return;
}
web::WebStateImpl* web_state_impl =
static_cast<web::WebStateImpl*>(web_state);
CRWWebController* web_controller = web_state_impl->GetWebController();
if (!web_controller) {
return;
}
NSURL* ns_url = script_message.frameInfo.request.URL;
base::Optional<GURL> url;
if (ns_url) {
url = net::GURLWithNSURL(ns_url);
}
ScriptMessage message(web::ValueResultFromWKResult(script_message.body),
web_controller.isUserInteracting,
script_message.frameInfo.mainFrame, url);
handler.Run(web_state, message);
}
} // namespace web