blob: 65c17d5c1bde975e1cad8178ecf9d904c3dfba42 [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.
#include "ios/web/public/js_messaging/java_script_feature.h"
#import <Foundation/Foundation.h>
#include "base/bind.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/web/js_messaging/java_script_content_world.h"
#import "ios/web/js_messaging/java_script_feature_manager.h"
#include "ios/web/js_messaging/page_script_util.h"
#include "ios/web/js_messaging/web_frame_internal.h"
#import "ios/web/public/js_messaging/web_frame.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Returns a JavaScript safe string based on |script_filename|. This is used as
// a unique identifier for a given script and passed to
// |MakeScriptInjectableOnce| which ensures JS isn't executed multiple times due
// to duplicate injection.
NSString* InjectionTokenForScript(NSString* script_filename) {
NSMutableCharacterSet* validCharacters =
[NSMutableCharacterSet alphanumericCharacterSet];
[validCharacters addCharactersInString:@"$_"];
NSCharacterSet* invalidCharacters = validCharacters.invertedSet;
NSString* token =
[script_filename stringByTrimmingCharactersInSet:invalidCharacters];
DCHECK_GT(token.length, 0ul);
return token;
}
} // namespace
namespace web {
#pragma mark - JavaScriptFeature::FeatureScript
JavaScriptFeature::FeatureScript
JavaScriptFeature::FeatureScript::CreateWithFilename(
const std::string& filename,
InjectionTime injection_time,
TargetFrames target_frames,
ReinjectionBehavior reinjection_behavior,
const PlaceholderReplacementsCallback& replacements_callback) {
return JavaScriptFeature::FeatureScript(filename, injection_time,
target_frames, reinjection_behavior,
replacements_callback);
}
JavaScriptFeature::FeatureScript::FeatureScript(
const std::string& filename,
InjectionTime injection_time,
TargetFrames target_frames,
ReinjectionBehavior reinjection_behavior,
const PlaceholderReplacementsCallback& replacements_callback)
: script_filename_(filename),
injection_time_(injection_time),
target_frames_(target_frames),
reinjection_behavior_(reinjection_behavior),
replacements_callback_(replacements_callback) {}
JavaScriptFeature::FeatureScript::FeatureScript(const FeatureScript&) = default;
JavaScriptFeature::FeatureScript& JavaScriptFeature::FeatureScript::operator=(
const FeatureScript&) = default;
JavaScriptFeature::FeatureScript::FeatureScript(FeatureScript&&) = default;
JavaScriptFeature::FeatureScript& JavaScriptFeature::FeatureScript::operator=(
FeatureScript&&) = default;
JavaScriptFeature::FeatureScript::~FeatureScript() = default;
NSString* JavaScriptFeature::FeatureScript::GetScriptString() const {
NSString* script_filename = base::SysUTF8ToNSString(script_filename_);
if (reinjection_behavior_ ==
ReinjectionBehavior::kReinjectOnDocumentRecreation) {
return ReplacePlaceholders(GetPageScript(script_filename));
}
// WKUserScript instances will automatically be re-injected by WebKit when the
// document is re-created, even though the JavaScript context will not be
// re-created. So the script needs to be wrapped in |MakeScriptInjectableOnce|
// so that is is not re-injected.
return MakeScriptInjectableOnce(
InjectionTokenForScript(script_filename),
ReplacePlaceholders(GetPageScript(script_filename)));
}
NSString* JavaScriptFeature::FeatureScript::ReplacePlaceholders(
NSString* script) const {
if (replacements_callback_.is_null())
return script;
PlaceholderReplacements replacements = replacements_callback_.Run();
if (!replacements)
return script;
for (NSString* key in replacements) {
script = [script stringByReplacingOccurrencesOfString:key
withString:replacements[key]];
}
return script;
}
#pragma mark - JavaScriptFeature
JavaScriptFeature::JavaScriptFeature(ContentWorld supported_world)
: supported_world_(supported_world), weak_factory_(this) {}
JavaScriptFeature::JavaScriptFeature(
ContentWorld supported_world,
std::vector<const FeatureScript> feature_scripts)
: supported_world_(supported_world),
scripts_(feature_scripts),
weak_factory_(this) {}
JavaScriptFeature::JavaScriptFeature(
ContentWorld supported_world,
std::vector<const FeatureScript> feature_scripts,
std::vector<const JavaScriptFeature*> dependent_features)
: supported_world_(supported_world),
scripts_(feature_scripts),
dependent_features_(dependent_features),
weak_factory_(this) {}
JavaScriptFeature::~JavaScriptFeature() = default;
JavaScriptFeature::ContentWorld JavaScriptFeature::GetSupportedContentWorld()
const {
return supported_world_;
}
const std::vector<const JavaScriptFeature::FeatureScript>
JavaScriptFeature::GetScripts() const {
return scripts_;
}
const std::vector<const JavaScriptFeature*>
JavaScriptFeature::GetDependentFeatures() const {
return dependent_features_;
}
base::Optional<std::string> JavaScriptFeature::GetScriptMessageHandlerName()
const {
return base::nullopt;
}
base::Optional<JavaScriptFeature::ScriptMessageHandler>
JavaScriptFeature::GetScriptMessageHandler() const {
if (!GetScriptMessageHandlerName()) {
return base::nullopt;
}
return base::BindRepeating(&JavaScriptFeature::ScriptMessageReceived,
weak_factory_.GetWeakPtr());
}
void JavaScriptFeature::ScriptMessageReceived(WebState* web_state,
const ScriptMessage& message) {}
bool JavaScriptFeature::CallJavaScriptFunction(
WebFrame* web_frame,
const std::string& function_name,
const std::vector<base::Value>& parameters) {
JavaScriptFeatureManager* feature_manager =
JavaScriptFeatureManager::FromBrowserState(web_frame->GetBrowserState());
DCHECK(feature_manager);
JavaScriptContentWorld* content_world =
feature_manager->GetContentWorldForFeature(this);
DCHECK(content_world);
return web_frame->GetWebFrameInternal()->CallJavaScriptFunctionInContentWorld(
function_name, parameters, content_world);
}
bool JavaScriptFeature::CallJavaScriptFunction(
WebFrame* web_frame,
const std::string& function_name,
const std::vector<base::Value>& parameters,
base::OnceCallback<void(const base::Value*)> callback,
base::TimeDelta timeout) {
JavaScriptFeatureManager* feature_manager =
JavaScriptFeatureManager::FromBrowserState(web_frame->GetBrowserState());
DCHECK(feature_manager);
JavaScriptContentWorld* content_world =
feature_manager->GetContentWorldForFeature(this);
DCHECK(content_world);
return web_frame->GetWebFrameInternal()->CallJavaScriptFunctionInContentWorld(
function_name, parameters, content_world, std::move(callback), timeout);
}
} // namespace web