Commit a1e8e6f4 authored by Mike Dougherty's avatar Mike Dougherty Committed by Chromium LUCI CQ

[iOS] Add JavaScriptFeature classes

Bug: 1042335
Change-Id: I5caadeedf9b94344f535c9171c381530820eaf41
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2555580
Commit-Queue: Mike Dougherty <michaeldo@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarAli Juma <ajuma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842439}
parent 6a3c0371
......@@ -37,10 +37,30 @@ source_set("js_messaging") {
]
}
source_set("java_script_feature") {
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":js_messaging",
"//base",
"//ios/web/public",
"//ios/web/public/js_messaging",
"//ios/web/web_state/ui:wk_web_view_configuration_provider_header",
]
sources = [
"java_script_content_world.h",
"java_script_content_world.mm",
"java_script_feature.mm",
"java_script_feature_manager.h",
"java_script_feature_manager.mm",
]
}
source_set("unittests") {
configs += [ "//build/config/compiler:enable_arc" ]
testonly = true
deps = [
":java_script_feature",
":js_messaging",
"//base",
"//base/test:test_support",
......@@ -56,6 +76,8 @@ source_set("unittests") {
sources = [
"crw_js_window_id_manager_unittest.mm",
"crw_wk_script_message_router_unittest.mm",
"java_script_content_world_unittest.mm",
"java_script_feature_unittest.mm",
"page_script_util_unittest.mm",
"web_frame_impl_unittest.mm",
"web_frame_util_unittest.mm",
......
// 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.
#ifndef IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_CONTENT_WORLD_H_
#define IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_CONTENT_WORLD_H_
#include <set>
#import <WebKit/WebKit.h>
namespace web {
class JavaScriptFeature;
// Represents a content world which can be configured with a given set of
// JavaScriptFeatures. An isolated world prevents the loaded web page’s
// JavaScript from interacting with the browser's feature JavaScript. This can
// improve the security and robustness of the feature JavaScript.
class JavaScriptContentWorld {
public:
~JavaScriptContentWorld();
JavaScriptContentWorld(const JavaScriptContentWorld&) = delete;
// Creates a content world for features which will interact with the page
// content world shared by the webpage's JavaScript.
explicit JavaScriptContentWorld(
WKUserContentController* user_content_controller);
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
// Creates a content world for features which will interact with the given
// |content_world|.
JavaScriptContentWorld(WKUserContentController* user_content_controller,
WKContentWorld* content_world)
API_AVAILABLE(ios(14.0));
#endif // defined(__IPHONE14_0)
// Adds |feature| by configuring the feature scripts and communication
// callbacks.
void AddFeature(const JavaScriptFeature* feature);
// Returns true if and only if |feature| has been added to this content world.
bool HasFeature(const JavaScriptFeature* feature);
private:
// The features which have already been configured for |content_world_|.
std::set<const JavaScriptFeature*> features_;
// The associated user content controller for configuring injected scripts and
// communication.
WKUserContentController* user_content_controller_ = nil;
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
// The associated WKContentWorld. May be null which represents the main world
// which the page content itself uses. (The same content world can also be
// represented by [WKContentWorld pageWorld] on iOS 14 and later.)
WKContentWorld* content_world_ API_AVAILABLE(ios(14.0)) = nullptr;
#endif // defined(__IPHONE14_0)
};
} // namespace web
#endif // IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_CONTENT_WORLD_H_
// 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 "ios/web/public/js_messaging/java_script_feature.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;
}
} // namespace
JavaScriptContentWorld::JavaScriptContentWorld(
WKUserContentController* user_content_controller)
: user_content_controller_(user_content_controller) {}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
JavaScriptContentWorld::JavaScriptContentWorld(
WKUserContentController* user_content_controller,
WKContentWorld* content_world)
: user_content_controller_(user_content_controller),
content_world_(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_ && content_world_ != WKContentWorld.pageWorld) {
DCHECK_EQ(feature->GetSupportedContentWorld(),
JavaScriptFeature::ContentWorld::kAnyContentWorld);
}
}
#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];
}
}
} // namespace web
// Copyright 2021 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"
#import "ios/web/public/js_messaging/java_script_feature.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
typedef PlatformTest JavaScriptContentWorldTest;
// Tests adding a JavaScriptFeature to a JavaScriptContentWorld.
TEST_F(JavaScriptContentWorldTest, AddFeature) {
WKUserContentController* user_content_controller =
[[WKUserContentController alloc] init];
web::JavaScriptContentWorld world(user_content_controller);
const web::JavaScriptFeature& feature = web::JavaScriptFeature(
web::JavaScriptFeature::ContentWorld::kAnyContentWorld, {});
world.AddFeature(&feature);
EXPECT_TRUE(world.HasFeature(&feature));
}
// Tests adding a JavaScriptFeature to a specific JavaScriptContentWorld.
TEST_F(JavaScriptContentWorldTest, AddFeatureToSpecificWKContentWorld) {
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
if (@available(iOS 14, *)) {
WKUserContentController* user_content_controller =
[[WKUserContentController alloc] init];
web::JavaScriptContentWorld world(user_content_controller,
[WKContentWorld defaultClientWorld]);
const web::JavaScriptFeature& feature = web::JavaScriptFeature(
web::JavaScriptFeature::ContentWorld::kAnyContentWorld, {});
world.AddFeature(&feature);
EXPECT_TRUE(world.HasFeature(&feature));
}
#endif // defined(__IPHONE14_0)
}
// 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_impl.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) {
return JavaScriptFeature::FeatureScript(filename, injection_time,
target_frames);
}
JavaScriptFeature::FeatureScript::FeatureScript(const std::string& filename,
InjectionTime injection_time,
TargetFrames target_frames)
: script_filename_(filename),
injection_time_(injection_time),
target_frames_(target_frames) {}
JavaScriptFeature::FeatureScript::~FeatureScript() = default;
NSString* JavaScriptFeature::FeatureScript::GetScriptString() const {
NSString* script_filename = base::SysUTF8ToNSString(script_filename_);
NSString* injection_token = InjectionTokenForScript(script_filename);
return MakeScriptInjectableOnce(injection_token,
GetPageScript(script_filename));
}
#pragma mark - JavaScriptFeature
JavaScriptFeature::JavaScriptFeature(ContentWorld supported_world)
: supported_world_(supported_world) {}
JavaScriptFeature::JavaScriptFeature(
ContentWorld supported_world,
std::vector<const FeatureScript> feature_scripts)
: supported_world_(supported_world), scripts_(feature_scripts) {}
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) {}
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_;
}
} // namespace web
// 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.
#ifndef IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_FEATURE_MANAGER_H_
#define IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_FEATURE_MANAGER_H_
#include <memory>
#include <vector>
#include "base/supports_user_data.h"
#import "ios/web/js_messaging/java_script_content_world.h"
@class WKUserContentController;
namespace web {
class BrowserState;
class JavaScriptFeature;
// Configures JavaScriptFeatures for |browser_state|. The features will be
// added to either |page_content_world_| or |isolated_world_| based on
// JavaScriptFeature::GetSupportedContentWorld() and the operating system of the
// user's device (which determines if isolated worlds are supported).
class JavaScriptFeatureManager : public base::SupportsUserData::Data {
public:
~JavaScriptFeatureManager() override;
// Returns the JavaScriptFeatureManager associated with |browser_state.|
// If a JavaScriptFeatureManager does not already exist, one will be created
// and associated with |browser_state|. |browser_state| must not be null.
static JavaScriptFeatureManager* FromBrowserState(
BrowserState* browser_state);
// Configures |features| on |user_content_controller_| by adding user scripts
// and script message handlers.
// NOTE: |page_content_world_| and |isolated_world_| will be recreated.
void ConfigureFeatures(std::vector<JavaScriptFeature*> features);
JavaScriptFeatureManager(const JavaScriptFeatureManager&) = delete;
JavaScriptFeatureManager& operator=(const JavaScriptFeatureManager&) = delete;
private:
JavaScriptFeatureManager(WKUserContentController* user_content_controller);
WKUserContentController* user_content_controller_ = nullptr;
// The content world shared with the page content JavaScript.
std::unique_ptr<JavaScriptContentWorld> page_content_world_;
// A content world isolated from the page content JavaScript for application
// JavaScript execution.
std::unique_ptr<JavaScriptContentWorld> isolated_world_;
};
} // namespace web
#endif // IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_FEATURE_MANAGER_H_
// 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_feature_manager.h"
#import <WebKit/WebKit.h>
#include "base/ios/ios_util.h"
#import "ios/web/public/browser_state.h"
#import "ios/web/public/js_messaging/java_script_feature.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Key used to associate a JavaScriptFeatureManager instances with a
// BrowserState.
const char kWebJavaScriptFeatureManagerKeyName[] =
"web_java_script_feature_manager";
} // namespace
namespace web {
JavaScriptFeatureManager::JavaScriptFeatureManager(
WKUserContentController* user_content_controller)
: user_content_controller_(user_content_controller) {}
JavaScriptFeatureManager::~JavaScriptFeatureManager() {}
JavaScriptFeatureManager* JavaScriptFeatureManager::FromBrowserState(
BrowserState* browser_state) {
DCHECK(browser_state);
JavaScriptFeatureManager* feature_manager =
static_cast<JavaScriptFeatureManager*>(
browser_state->GetUserData(kWebJavaScriptFeatureManagerKeyName));
if (!feature_manager) {
WKWebViewConfigurationProvider& configuration_provider =
WKWebViewConfigurationProvider::FromBrowserState(browser_state);
WKUserContentController* user_content_controller =
configuration_provider.GetWebViewConfiguration().userContentController;
feature_manager = new JavaScriptFeatureManager(user_content_controller);
browser_state->SetUserData(kWebJavaScriptFeatureManagerKeyName,
base::WrapUnique(feature_manager));
}
return feature_manager;
}
void JavaScriptFeatureManager::ConfigureFeatures(
std::vector<JavaScriptFeature*> features) {
page_content_world_ =
std::make_unique<JavaScriptContentWorld>(user_content_controller_);
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
if (@available(iOS 14, *)) {
isolated_world_ = std::make_unique<JavaScriptContentWorld>(
user_content_controller_, WKContentWorld.defaultClientWorld);
}
#endif // defined(__IPHONE14_0)
for (JavaScriptFeature* feature : features) {
if (isolated_world_ &&
feature->GetSupportedContentWorld() ==
JavaScriptFeature::ContentWorld::kAnyContentWorld) {
isolated_world_->AddFeature(feature);
} else {
page_content_world_->AddFeature(feature);
}
}
}
} // namespace web
// Copyright 2021 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/public/js_messaging/java_script_feature.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
typedef PlatformTest JavaScriptFeatureTest;
// Tests the creation of FeatureScripts.
TEST_F(JavaScriptFeatureTest, CreateFeatureScript) {
auto document_start_injection_time =
web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
auto target_frames_all =
web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
auto feature_script =
web::JavaScriptFeature::FeatureScript::CreateWithFilename(
"base_js", document_start_injection_time, target_frames_all);
EXPECT_EQ(document_start_injection_time, feature_script.GetInjectionTime());
EXPECT_EQ(target_frames_all, feature_script.GetTargetFrames());
EXPECT_TRUE([feature_script.GetScriptString() containsString:@"__gCrWeb"]);
auto document_end_injection_time =
web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd;
auto target_frames_main =
web::JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame;
auto feature_script2 =
web::JavaScriptFeature::FeatureScript::CreateWithFilename(
"common_js", document_end_injection_time, target_frames_main);
EXPECT_EQ(document_end_injection_time, feature_script2.GetInjectionTime());
EXPECT_EQ(target_frames_main, feature_script2.GetTargetFrames());
EXPECT_TRUE(
[feature_script2.GetScriptString() containsString:@"__gCrWeb.common"]);
}
// Tests creating a JavaScriptFeature.
TEST_F(JavaScriptFeatureTest, CreateFeature) {
auto document_start_injection_time =
web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
auto target_frames_all =
web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
const web::JavaScriptFeature::FeatureScript feature_script =
web::JavaScriptFeature::FeatureScript::CreateWithFilename(
"base_js", document_start_injection_time, target_frames_all);
auto any_content_world =
web::JavaScriptFeature::ContentWorld::kAnyContentWorld;
web::JavaScriptFeature feature(any_content_world, {feature_script});
EXPECT_EQ(any_content_world, feature.GetSupportedContentWorld());
EXPECT_EQ(0ul, feature.GetDependentFeatures().size());
auto feature_scripts = feature.GetScripts();
ASSERT_EQ(1ul, feature_scripts.size());
EXPECT_NSEQ(feature_script.GetScriptString(),
feature_scripts[0].GetScriptString());
}
// Tests creating a JavaScriptFeature which relies on a dependent feature.
TEST_F(JavaScriptFeatureTest, CreateFeatureWithDependentFeature) {
auto document_start_injection_time =
web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
auto target_frames_all =
web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
const web::JavaScriptFeature::FeatureScript dependent_feature_script =
web::JavaScriptFeature::FeatureScript::CreateWithFilename(
"base_js", document_start_injection_time, target_frames_all);
auto document_end_injection_time =
web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd;
auto target_frames_main =
web::JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame;
const web::JavaScriptFeature::FeatureScript feature_script =
web::JavaScriptFeature::FeatureScript::CreateWithFilename(
"common_js", document_end_injection_time, target_frames_main);
auto page_content_world =
web::JavaScriptFeature::ContentWorld::kPageContentWorld;
web::JavaScriptFeature dependent_feature(page_content_world,
{dependent_feature_script});
web::JavaScriptFeature feature(page_content_world, {feature_script},
{&dependent_feature});
EXPECT_EQ(page_content_world, feature.GetSupportedContentWorld());
auto feature_scripts = feature.GetScripts();
ASSERT_EQ(1ul, feature_scripts.size());
auto dependent_features = feature.GetDependentFeatures();
ASSERT_EQ(1ul, dependent_features.size());
auto dependent_feature_scripts = dependent_features[0]->GetScripts();
ASSERT_EQ(1ul, dependent_feature_scripts.size());
EXPECT_NSEQ(feature_script.GetScriptString(),
feature_scripts[0].GetScriptString());
EXPECT_NSEQ(dependent_feature_script.GetScriptString(),
dependent_feature_scripts[0].GetScriptString());
}
......@@ -15,6 +15,20 @@ class BrowserState;
// bundled resource file with the given name (excluding extension).
NSString* GetPageScript(NSString* script_file_name);
// Make sure that script is injected only once. For example, content of
// WKUserScript can be injected into the same page multiple times
// without notifying WKNavigationDelegate (e.g. after window.document.write
// JavaScript call). Injecting the script multiple times invalidates the
// __gCrWeb.windowId variable and will break the ability to send messages from
// JS to the native code. Wrapping injected script into "if (!injected)" check
// prevents multiple injections into the same page. |script_identifier| should
// identify the script being injected in order to enforce the injection of
// |script| to only once.
// NOTE: |script_identifier| will be used as the suffix for a JavaScript var, so
// it must adhere to JavaScript var naming rules.
NSString* MakeScriptInjectableOnce(NSString* script_identifier,
NSString* script);
// Returns an autoreleased string containing the JavaScript to be injected into
// the main frame of the web view as early as possible.
NSString* GetDocumentStartScriptForMainFrame(BrowserState* browser_state);
......
......@@ -18,29 +18,6 @@
namespace {
// Make sure that script is injected only once. For example, content of
// WKUserScript can be injected into the same page multiple times
// without notifying WKNavigationDelegate (e.g. after window.document.write
// JavaScript call). Injecting the script multiple times invalidates the
// __gCrWeb.windowId variable and will break the ability to send messages from
// JS to the native code. Wrapping injected script into "if (!injected)" check
// prevents multiple injections into the same page. |script_identifier| should
// identify the script being injected in order to enforce the injection of
// |script| to only once.
// NOTE: |script_identifier| will be used as the prefix for a JavaScript var, so
// it must adhere to JavaScript var naming rules.
NSString* MakeScriptInjectableOnce(NSString* script_identifier,
NSString* script) {
NSString* kOnceWrapperTemplate =
@"if (typeof %@ === 'undefined') { var %@ = true; %%@ }";
NSString* injected_var_name =
[NSString stringWithFormat:@"%@_injected", script_identifier];
NSString* once_wrapper =
[NSString stringWithFormat:kOnceWrapperTemplate, injected_var_name,
injected_var_name];
return [NSString stringWithFormat:once_wrapper, script];
}
// Returns a string with \ and ' escaped.
// This is used instead of GetQuotedJSONString because that will convert
// UTF-16 to UTF-8, which can cause problems when injecting scripts depending
......@@ -72,6 +49,18 @@ NSString* GetPageScript(NSString* script_file_name) {
return content;
}
NSString* MakeScriptInjectableOnce(NSString* script_identifier,
NSString* script) {
NSString* kOnceWrapperTemplate =
@"if (typeof %@ === 'undefined') { var %@ = true; %%@ }";
NSString* injected_var_name =
[NSString stringWithFormat:@"_injected_%@", script_identifier];
NSString* once_wrapper =
[NSString stringWithFormat:kOnceWrapperTemplate, injected_var_name,
injected_var_name];
return [NSString stringWithFormat:once_wrapper, script];
}
NSString* GetDocumentStartScriptForMainFrame(BrowserState* browser_state) {
DCHECK(GetWebClient());
NSString* embedder_page_script =
......
......@@ -24,6 +24,20 @@
using base::test::ios::WaitUntilConditionOrTimeout;
using base::test::ios::kWaitForPageLoadTimeout;
namespace {
void AddSharedScriptsToWebView(WKWebView* web_view) {
// Scripts must be all injected at once because as soon as __gCrWeb exists,
// injection is assumed to be done and __gCrWeb.message is used.
NSString* scripts = [NSString
stringWithFormat:@"%@; %@; %@", web::test::GetPageScript(@"base_js"),
web::test::GetPageScript(@"common_js"),
web::test::GetPageScript(@"message_js")];
web::test::ExecuteJavaScript(web_view, scripts);
}
} // namespace
namespace web {
namespace {
......@@ -37,10 +51,26 @@ class PageScriptUtilTest : public WebTest {
}
};
// Tests that |MakeScriptInjectableOnce| prevents a script from being injected
// twice.
TEST_F(PageScriptUtilTest, MakeScriptInjectableOnce) {
WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
NSString* identifier = @"script_id";
test::ExecuteJavaScript(
web_view, MakeScriptInjectableOnce(identifier, @"var value = 1;"));
EXPECT_NSEQ(@(1), test::ExecuteJavaScript(web_view, @"value"));
test::ExecuteJavaScript(web_view,
MakeScriptInjectableOnce(identifier, @"value = 2;"));
EXPECT_NSEQ(@(1), test::ExecuteJavaScript(web_view, @"value"));
}
// Tests that WKWebView early page script is a valid script that injects global
// __gCrWeb object.
TEST_F(PageScriptUtilTest, WKWebViewEarlyPageScript) {
WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
AddSharedScriptsToWebView(web_view);
test::ExecuteJavaScript(
web_view, GetDocumentStartScriptForAllFrames(GetBrowserState()));
EXPECT_NSEQ(@"object", test::ExecuteJavaScript(web_view, @"typeof __gCrWeb"));
......@@ -50,6 +80,7 @@ TEST_F(PageScriptUtilTest, WKWebViewEarlyPageScript) {
TEST_F(PageScriptUtilTest, WKEmbedderScript) {
GetWebClient()->SetEarlyPageScript(@"__gCrEmbedder = {};");
WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
AddSharedScriptsToWebView(web_view);
test::ExecuteJavaScript(
web_view, GetDocumentStartScriptForAllFrames(GetBrowserState()));
test::ExecuteJavaScript(
......
......@@ -10,6 +10,7 @@ source_set("js_messaging") {
]
sources = [
"java_script_feature.h",
"web_frame.h",
"web_frame_user_data.h",
"web_frame_util.h",
......
// 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.
#ifndef IOS_WEB_PUBLIC_JS_MESSAGING_JAVA_SCRIPT_FEATURE_H_
#define IOS_WEB_PUBLIC_JS_MESSAGING_JAVA_SCRIPT_FEATURE_H_
#include <string>
#include <vector>
@class NSString;
namespace web {
// Describes a feature implemented in Javascript and native<->JS communication
// (if any). It is intended to be instantiated directly for simple features
// requiring injection only, but should subclassed into feature specific classes
// to handle JS<->native communication.
// NOTE: As implemented within //ios/web, JavaScriptFeature instances holds no
// state itself and can be used application-wide across browser states. However,
// this is not guaranteed of JavaScriptFeature subclasses.
class JavaScriptFeature {
public:
// The content world which this feature supports.
// NOTE: Features should use kAnyContentWorld whenever possible to allow for
// isolation between the feature and the loaded webpage JavaScript.
enum class ContentWorld {
// Represents any content world.
kAnyContentWorld = 0,
// Represents the page content world which is shared by the JavaScript of
// the webpage. This value should only be used if the feature provides
// JavaScript which needs to be accessible to the client JavaScript. For
// example, JavaScript polyfills.
kPageContentWorld,
};
// A script to be injected into webpage frames which support this feature.
class FeatureScript {
public:
// The time at which this script will be injected into the page.
enum class InjectionTime {
kDocumentStart = 0,
kDocumentEnd,
};
// The frames which this script will be injected into.
enum class TargetFrames {
kAllFrames = 0,
kMainFrame,
};
// Creates a FeatureScript with the script file from the application bundle
// with |filename| to be injected at |injection_time| into |target_frames|.
static FeatureScript CreateWithFilename(const std::string& filename,
InjectionTime injection_time,
TargetFrames target_frames);
// Returns the JavaScript string of the script with |script_filename_|.
NSString* GetScriptString() const;
InjectionTime GetInjectionTime() const { return injection_time_; }
TargetFrames GetTargetFrames() const { return target_frames_; }
~FeatureScript();
private:
FeatureScript(const std::string& filename,
InjectionTime injection_time,
TargetFrames target_frames);
std::string script_filename_;
InjectionTime injection_time_;
TargetFrames target_frames_;
};
JavaScriptFeature(ContentWorld supported_world,
std::vector<const FeatureScript> feature_scripts);
JavaScriptFeature(ContentWorld supported_world,
std::vector<const FeatureScript> feature_scripts,
std::vector<const JavaScriptFeature*> dependent_features);
virtual ~JavaScriptFeature();
// Returns the supported content world for this feature.
ContentWorld GetSupportedContentWorld() const;
// Returns a vector of scripts used by this feature.
virtual const std::vector<const FeatureScript> GetScripts() const;
// Returns a vector of features which this one depends upon being available.
virtual const std::vector<const JavaScriptFeature*> GetDependentFeatures()
const;
JavaScriptFeature(const JavaScriptFeature&) = delete;
protected:
explicit JavaScriptFeature(ContentWorld supported_world);
private:
ContentWorld supported_world_;
std::vector<const FeatureScript> scripts_;
std::vector<const JavaScriptFeature*> dependent_features_;
};
} // namespace web
#endif // IOS_WEB_PUBLIC_JS_MESSAGING_JAVA_SCRIPT_FEATURE_H_
......@@ -117,7 +117,14 @@ source_set("crw_context_menu_controller") {
configs += [ "//build/config/compiler:enable_arc" ]
}
source_set("wk_web_view_configuration_provider_header") {
deps = [ "//base" ]
sources = [ "wk_web_view_configuration_provider.h" ]
configs += [ "//build/config/compiler:enable_arc" ]
}
source_set("wk_web_view_configuration_provider") {
public_deps = [ ":wk_web_view_configuration_provider_header" ]
deps = [
"//base",
"//components/safe_browsing/core:features",
......@@ -134,7 +141,6 @@ source_set("wk_web_view_configuration_provider") {
"wk_content_rule_list_provider.mm",
"wk_content_rule_list_util.h",
"wk_content_rule_list_util.mm",
"wk_web_view_configuration_provider.h",
"wk_web_view_configuration_provider.mm",
"wk_web_view_configuration_provider_observer.h",
]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment