Skip to content

Commit

Permalink
[iOS] Added a capability to inject CWVUserScript into all frames.
Browse files Browse the repository at this point in the history
This CL is making CWVUserScript API consistent with WKUserScript API by adding an extra parameter which allows the injection of user scripts into all frames.

Bug: 1450737
Change-Id: Iad51cd1f3b800785ee947d164989b1b99f079147
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4582892
Reviewed-by: Ali Juma <ajuma@chromium.org>
Reviewed-by: Mike Dougherty <michaeldo@chromium.org>
Commit-Queue: Anton Ostrovskii <aostrovskii@google.com>
Cr-Commit-Position: refs/heads/main@{#1158395}
  • Loading branch information
Anton Ostrovskii authored and Chromium LUCI CQ committed Jun 15, 2023
1 parent 4f18b14 commit 781cc43
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 64 deletions.
2 changes: 1 addition & 1 deletion ios/web/js_messaging/page_script_util_unittest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ void AddSharedScriptsToWebView(WKWebView* web_view) {

// Tests that embedder's WKWebView script is included into early script.
TEST_F(PageScriptUtilTest, WKEmbedderScript) {
GetWebClient()->SetEarlyPageScript(@"__gCrEmbedder = {};");
GetWebClient()->SetEarlyPageScriptForMainFrame(@"__gCrEmbedder = {};");
WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
AddSharedScriptsToWebView(web_view);
test::ExecuteJavaScript(
Expand Down
12 changes: 9 additions & 3 deletions ios/web/public/test/fakes/fake_web_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class FakeWebClient : public web::WebClient {
std::vector<JavaScriptFeature*> GetJavaScriptFeatures(
BrowserState* browser_state) const override;

NSString* GetDocumentStartScriptForAllFrames(
BrowserState* browser_state) const override;
NSString* GetDocumentStartScriptForMainFrame(
BrowserState* browser_state) const override;
void PrepareErrorPage(WebState* web_state,
Expand All @@ -55,8 +57,11 @@ class FakeWebClient : public web::WebClient {
// Sets `plugin_not_supported_text_`.
void SetPluginNotSupportedText(const std::u16string& text);

// Changes Early Page Script for testing purposes.
void SetEarlyPageScript(NSString* page_script);
// Changes Early Page Script for all frames for testing purposes.
void SetEarlyPageScriptForAllFrames(NSString* page_script_for_all_frames);

// Changes Early Page Script for the main frame for testing purposes.
void SetEarlyPageScriptForMainFrame(NSString* page_script_for_main_frame);

// Changes Java Script Features for testing.
void SetJavaScriptFeatures(std::vector<JavaScriptFeature*> features);
Expand All @@ -83,7 +88,8 @@ class FakeWebClient : public web::WebClient {
private:
std::u16string plugin_not_supported_text_;
std::vector<JavaScriptFeature*> java_script_features_;
NSString* early_page_script_ = nil;
NSString* early_page_script_for_all_frames_ = nil;
NSString* early_page_script_for_main_frame_ = nil;
UserAgentType default_user_agent_ = UserAgentType::MOBILE;
CRWFakeFindSession* find_session_prototype_ API_AVAILABLE(ios(16)) = nil;
bool text_search_started_ = false;
Expand Down
17 changes: 14 additions & 3 deletions ios/web/public/test/fakes/fake_web_client.mm
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@
return java_script_features_;
}

NSString* FakeWebClient::GetDocumentStartScriptForAllFrames(
BrowserState* browser_state) const {
return early_page_script_for_all_frames_ ?: @"";
}

NSString* FakeWebClient::GetDocumentStartScriptForMainFrame(
BrowserState* browser_state) const {
return early_page_script_ ? early_page_script_ : @"";
return early_page_script_for_main_frame_ ?: @"";
}

void FakeWebClient::SetPluginNotSupportedText(const std::u16string& text) {
Expand All @@ -67,8 +72,14 @@
java_script_features_ = features;
}

void FakeWebClient::SetEarlyPageScript(NSString* page_script) {
early_page_script_ = [page_script copy];
void FakeWebClient::SetEarlyPageScriptForAllFrames(
NSString* page_script_for_all_frames) {
early_page_script_for_all_frames_ = [page_script_for_all_frames copy];
}

void FakeWebClient::SetEarlyPageScriptForMainFrame(
NSString* page_script_for_main_frame) {
early_page_script_for_main_frame_ = [page_script_for_main_frame copy];
}

void FakeWebClient::PrepareErrorPage(
Expand Down
124 changes: 100 additions & 24 deletions ios/web/web_state/ui/wk_web_view_configuration_provider_unittest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@
EXPECT_FALSE(config);
}

// Tests that configuration's userContentController has only one script with the
// same content as web::GetDocumentStartScriptForMainFrame() returns.
// Tests that configuration's userContentController has both all_frames script
// and a main_frame script with the same content as
// web::GetDocumentStartScriptForAllFrames() and
// web::GetDocumentStartScriptForMainFrame() returns respectively.
TEST_F(WKWebViewConfigurationProviderTest, UserScript) {
WKUserContentController* user_content_controller =
GetProvider().GetWebViewConfiguration().userContentController;
Expand All @@ -164,40 +166,114 @@
GetDocumentStartScriptForMainFrame(&browser_state_));
ASSERT_TRUE(main_frame_script);
EXPECT_TRUE(main_frame_script.isForMainFrameOnly);

EXPECT_NE(early_all_user_script.source, main_frame_script.source);
EXPECT_TRUE([early_all_user_script.source containsString:@"all_frames"]);
EXPECT_TRUE([main_frame_script.source containsString:@"main_frame"]);
}

// Tests that configuration's userContentController has different scripts after
// the scripts are updated.
TEST_F(WKWebViewConfigurationProviderTest, UpdateScripts) {
// Tests that configuration's userContentController has different main frame
// scripts after the main frame scripts are updated. Verifies that all frame
// scripts were no altered while updating the main frame scripts.
TEST_F(WKWebViewConfigurationProviderTest, UpdateMainFrameScripts) {
FakeWebClient* client = GetWebClient();
client->SetEarlyPageScript(@"var test = 4;");
client->SetEarlyPageScriptForMainFrame(@"var test = 4;");

WKUserContentController* user_content_controller =
GetProvider().GetWebViewConfiguration().userContentController;

NSString* initial_main_frame_script =
NSString* initial_all_frames_script_source =
GetDocumentStartScriptForAllFrames(&browser_state_);
WKUserScript* initial_all_frames_script = FindWKUserScriptContaining(
user_content_controller.userScripts, initial_all_frames_script_source);
EXPECT_TRUE(initial_all_frames_script);

NSString* initial_main_frame_script_source =
GetDocumentStartScriptForMainFrame(&browser_state_);
WKUserScript* initial_main_frame_script = FindWKUserScriptContaining(
user_content_controller.userScripts, initial_main_frame_script_source);
EXPECT_TRUE(initial_main_frame_script);

client->SetEarlyPageScriptForMainFrame(@"var test = 3;");
GetProvider().UpdateScripts();

NSString* updated_main_frame_script_source =
GetDocumentStartScriptForMainFrame(&browser_state_);
WKUserScript* initial_script = FindWKUserScriptContaining(
user_content_controller.userScripts, initial_main_frame_script);
EXPECT_TRUE(initial_script);
WKUserScript* updated_main_frame_script = FindWKUserScriptContaining(
user_content_controller.userScripts, updated_main_frame_script_source);
EXPECT_TRUE(updated_main_frame_script);

EXPECT_NE(updated_main_frame_script_source, initial_main_frame_script_source);
EXPECT_NE(initial_main_frame_script.source, updated_main_frame_script.source);
EXPECT_LT(0U, [updated_main_frame_script.source
rangeOfString:updated_main_frame_script_source]
.length);
EXPECT_EQ(0U, [initial_main_frame_script.source
rangeOfString:updated_main_frame_script_source]
.length);

NSString* updated_all_frames_script_source =
GetDocumentStartScriptForAllFrames(&browser_state_);
WKUserScript* updated_all_frames_script = FindWKUserScriptContaining(
user_content_controller.userScripts, updated_all_frames_script_source);
EXPECT_TRUE(updated_all_frames_script);

EXPECT_TRUE([updated_all_frames_script_source
isEqualToString:initial_all_frames_script_source]);
EXPECT_TRUE([initial_all_frames_script.source
isEqualToString:updated_all_frames_script.source]);
}

// Tests that configuration's userContentController has different all frames
// scripts after the all frames scripts are updated. Verifies that main frame
// scripts were no altered while updating the all frames scripts.
TEST_F(WKWebViewConfigurationProviderTest, UpdateAllFramesScripts) {
FakeWebClient* client = GetWebClient();
client->SetEarlyPageScriptForAllFrames(@"var test = 4;");

WKUserContentController* user_content_controller =
GetProvider().GetWebViewConfiguration().userContentController;

NSString* initial_main_frame_script_source =
GetDocumentStartScriptForMainFrame(&browser_state_);
WKUserScript* initial_main_frame_script = FindWKUserScriptContaining(
user_content_controller.userScripts, initial_main_frame_script_source);
EXPECT_TRUE(initial_main_frame_script);

NSString* initial_all_frames_script_source =
GetDocumentStartScriptForAllFrames(&browser_state_);
WKUserScript* initial_all_frames_script = FindWKUserScriptContaining(
user_content_controller.userScripts, initial_all_frames_script_source);
EXPECT_TRUE(initial_all_frames_script);

client->SetEarlyPageScript(@"var test = 3;");
client->SetEarlyPageScriptForAllFrames(@"var test = 3;");
GetProvider().UpdateScripts();

NSString* updated_main_frame_script =
NSString* updated_all_frames_script_source =
GetDocumentStartScriptForAllFrames(&browser_state_);
WKUserScript* updated_all_frames_script = FindWKUserScriptContaining(
user_content_controller.userScripts, updated_all_frames_script_source);
EXPECT_TRUE(updated_all_frames_script);

EXPECT_NE(updated_all_frames_script_source, initial_all_frames_script_source);
EXPECT_NE(initial_all_frames_script.source, updated_all_frames_script.source);
EXPECT_LT(0U, [updated_all_frames_script.source
rangeOfString:updated_all_frames_script_source]
.length);
EXPECT_EQ(0U, [initial_all_frames_script.source
rangeOfString:updated_all_frames_script_source]
.length);

NSString* updated_main_frame_script_source =
GetDocumentStartScriptForMainFrame(&browser_state_);
WKUserScript* updated_script = FindWKUserScriptContaining(
user_content_controller.userScripts, updated_main_frame_script);
EXPECT_TRUE(updated_script);

EXPECT_NE(updated_main_frame_script, initial_main_frame_script);
EXPECT_NE(initial_script.source, updated_script.source);
EXPECT_LT(
0U,
[updated_script.source rangeOfString:updated_main_frame_script].length);
EXPECT_EQ(
0U,
[initial_script.source rangeOfString:updated_main_frame_script].length);
WKUserScript* updated_main_frame_script = FindWKUserScriptContaining(
user_content_controller.userScripts, updated_main_frame_script_source);
EXPECT_TRUE(updated_main_frame_script);

EXPECT_TRUE([updated_main_frame_script_source
isEqualToString:initial_main_frame_script_source]);
EXPECT_TRUE([initial_main_frame_script.source
isEqualToString:updated_main_frame_script.source]);
}

// Tests that configuration's userContentController has additional scripts
Expand Down
14 changes: 10 additions & 4 deletions ios/web_view/internal/cwv_user_content_controller.mm
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,22 @@ - (void)removeAllUserScripts {
// Updates the early page script associated with the BrowserState with the
// content of _userScripts.
- (void)updateEarlyPageScript {
NSMutableString* joinedScript = [[NSMutableString alloc] init];
NSMutableString* joinedAllFramesScript = [[NSMutableString alloc] init];
NSMutableString* joinedMainFrameScript = [[NSMutableString alloc] init];
for (CWVUserScript* script in _userScripts) {
[joinedScript appendString:script.source];
// Inserts "\n" between scripts to make it safer to join multiple scripts,
// in case the first script doesn't end with ";" or "\n".
[joinedScript appendString:@"\n"];
if (script.isForMainFrameOnly) {
[joinedMainFrameScript appendString:script.source];
[joinedMainFrameScript appendString:@"\n"];
} else {
[joinedAllFramesScript appendString:script.source];
[joinedAllFramesScript appendString:@"\n"];
}
}
ios_web_view::WebViewEarlyPageScriptProvider::FromBrowserState(
_configuration.browserState)
.SetScript(joinedScript);
.SetScripts(joinedAllFramesScript, joinedMainFrameScript);
}

- (void)addMessageHandler:(void (^)(NSDictionary* payload))handler
Expand Down
8 changes: 8 additions & 0 deletions ios/web_view/internal/cwv_user_script.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ @implementation CWVUserScript

@synthesize source = _source;

@synthesize forMainFrameOnly = _forMainFrameOnly;

- (nonnull instancetype)initWithSource:(nonnull NSString*)source {
return [self initWithSource:source forMainFrameOnly:true];
}

- (nonnull instancetype)initWithSource:(nonnull NSString*)source
forMainFrameOnly:(BOOL)forMainFrameOnly {
self = [super init];
if (self) {
_source = [source copy];
_forMainFrameOnly = forMainFrameOnly;
}
return self;
}
Expand Down
20 changes: 15 additions & 5 deletions ios/web_view/internal/web_view_early_page_script_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,28 @@ class WebViewEarlyPageScriptProvider : public base::SupportsUserData::Data {
static WebViewEarlyPageScriptProvider& FromBrowserState(
web::BrowserState* _Nonnull browser_state);

// Getter and Setter for the JavaScript source code.
NSString* _Nonnull GetScript() { return script_; }
void SetScript(NSString* _Nonnull script);
// Setter for the JavaScript source code which should be injected into all
// pages as early as possible.
void SetScripts(NSString* _Nonnull all_frames_script,
NSString* _Nonnull main_frame_script);

// Getter for the JavaScript source code intended for all frames.
NSString* _Nonnull GetAllFramesScript() { return all_frames_script_; }

// Getter for the JavaScript source code intended for the main frame only.
NSString* _Nonnull GetMainFrameScript() { return main_frame_script_; }

private:
WebViewEarlyPageScriptProvider(web::BrowserState* _Nonnull browser_state);

// The associated browser state.
web::BrowserState* _Nonnull browser_state_;

// The JavaScript source code.
NSString* _Nonnull script_;
// The JavaScript source code intended for all frames.
NSString* _Nonnull all_frames_script_;

// The JavaScript source code intended for the main frame only.
NSString* _Nonnull main_frame_script_;
};

} // namespace ios_web_view
Expand Down
11 changes: 8 additions & 3 deletions ios/web_view/internal/web_view_early_page_script_provider.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@

WebViewEarlyPageScriptProvider::~WebViewEarlyPageScriptProvider() = default;

void WebViewEarlyPageScriptProvider::SetScript(NSString* _Nonnull script) {
script_ = [script copy];
void WebViewEarlyPageScriptProvider::SetScripts(
NSString* _Nonnull all_frames_script,
NSString* _Nonnull main_frame_script) {
all_frames_script_ = [all_frames_script copy];
main_frame_script_ = [main_frame_script copy];

// Early page scripts must be explicitly updated after they change.
web::WKWebViewConfigurationProvider& config_provider =
Expand All @@ -52,6 +55,8 @@

WebViewEarlyPageScriptProvider::WebViewEarlyPageScriptProvider(
web::BrowserState* _Nonnull browser_state)
: browser_state_(browser_state), script_([[NSString alloc] init]) {}
: browser_state_(browser_state),
all_frames_script_([[NSString alloc] init]),
main_frame_script_([[NSString alloc] init]) {}

} // namespace ios_web_view
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,20 @@ bool UserScriptsContainString(NSString* string) {
}
};

// Test WebViewEarlyPageScriptProvder::SetScript properly updates the underlying
// WKUserContentController.
TEST_F(WebViewEarlyPageScriptProviderTest, SetScript) {
EXPECT_FALSE(UserScriptsContainString(@"WebViewEarlyPageScriptProvider"));
// Test WebViewEarlyPageScriptProvder::SetScripts properly updates the
// underlying WKUserContentController.
TEST_F(WebViewEarlyPageScriptProviderTest, SetScripts) {
NSString* allFramesScript = @"WebViewEarlyPageScriptProvider-AllFrames";
NSString* mainFrameScript = @"WebViewEarlyPageScriptProvider-MainFrame";

EXPECT_FALSE(UserScriptsContainString(allFramesScript));
EXPECT_FALSE(UserScriptsContainString(mainFrameScript));

WebViewEarlyPageScriptProvider::FromBrowserState(&browser_state_)
.SetScript(@"WebViewEarlyPageScriptProvider");
EXPECT_TRUE(UserScriptsContainString(@"WebViewEarlyPageScriptProvider"));
.SetScripts(allFramesScript, mainFrameScript);

EXPECT_TRUE(UserScriptsContainString(allFramesScript));
EXPECT_TRUE(UserScriptsContainString(mainFrameScript));
}

} // namespace ios_web_view
2 changes: 2 additions & 0 deletions ios/web_view/internal/web_view_web_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class WebViewWebClient : public web::WebClient {
base::RefCountedMemory* GetDataResourceBytes(int resource_id) const override;
std::vector<web::JavaScriptFeature*> GetJavaScriptFeatures(
web::BrowserState* browser_state) const override;
NSString* GetDocumentStartScriptForAllFrames(
web::BrowserState* browser_state) const override;
NSString* GetDocumentStartScriptForMainFrame(
web::BrowserState* browser_state) const override;
void PrepareErrorPage(web::WebState* web_state,
Expand Down
9 changes: 8 additions & 1 deletion ios/web_view/internal/web_view_web_client.mm
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,18 @@
WebViewMessageHandlerJavaScriptFeature::FromBrowserState(browser_state)};
}

NSString* WebViewWebClient::GetDocumentStartScriptForAllFrames(
web::BrowserState* browser_state) const {
WebViewEarlyPageScriptProvider& provider =
WebViewEarlyPageScriptProvider::FromBrowserState(browser_state);
return provider.GetAllFramesScript();
}

NSString* WebViewWebClient::GetDocumentStartScriptForMainFrame(
web::BrowserState* browser_state) const {
WebViewEarlyPageScriptProvider& provider =
WebViewEarlyPageScriptProvider::FromBrowserState(browser_state);
return provider.GetScript();
return provider.GetMainFrameScript();
}

void WebViewWebClient::PrepareErrorPage(
Expand Down
3 changes: 3 additions & 0 deletions ios/web_view/public/cwv_defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,7 @@
// webView:decidePolicyForNavigationResponse:decisionHandler:] APIs.
#define IOS_WEB_VIEW_SUPPORTS_ASYNCCHRONOUS_POLICY_DECISION_HANDLER 1

// Supports -[CWVUserScript initWithSource:forMainFrameOnly:].
#define IOS_WEB_VIEW_SUPPORTS_INSTALLING_USER_SCRIPTS_INTO_ALL_FRAMES 1

#endif // IOS_WEB_VIEW_PUBLIC_CWV_DEFINES_H_

0 comments on commit 781cc43

Please sign in to comment.