Commit 682eaa50 authored by David Jean's avatar David Jean Committed by Commit Bot

Reland "[ios] extract JS injection from web controller"

This is a reland of 950edae3

Original change's description:
> [ios] extract JS injection from web controller
> 
> Moved JS injection relative code to a new class in crw_js_injector.
> Updated CRWJSInjectionEvaluator to have a executeUserJavaScript that
> seemed to logicaly have been there already.
> 
> Bug: 954137
> Change-Id: I3a80c995de82329f22532d791c20d84b9ecbfe46
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1571703
> Commit-Queue: David Jean <djean@chromium.org>
> Reviewed-by: Eugene But <eugenebut@chromium.org>
> Reviewed-by: Mike Dougherty <michaeldo@chromium.org>
> Reviewed-by: Mark Cogan <marq@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#657195}

Bug: 954137
Change-Id: I1b4950cc3bf5c7ebe0f38b82c080324bd42c845f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1601051
Commit-Queue: David Jean <djean@chromium.org>
Reviewed-by: default avatarMike Dougherty <michaeldo@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661292}
parent dcad7439
...@@ -37,6 +37,11 @@ ...@@ -37,6 +37,11 @@
web::ExecuteJavaScript(_webView, script, completionHandler); web::ExecuteJavaScript(_webView, script, completionHandler);
} }
- (void)executeUserJavaScript:(NSString*)script
completionHandler:(void (^)(id, NSError*))completionHandler {
web::ExecuteJavaScript(_webView, script, completionHandler);
}
- (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass { - (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass {
return [_injectedScriptManagers containsObject:injectionManagerClass]; return [_injectedScriptManagers containsObject:injectionManagerClass];
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#import "ios/web/public/web_client.h" #import "ios/web/public/web_client.h"
#include "ios/web/public/web_state/url_verification_constants.h" #include "ios/web/public/web_state/url_verification_constants.h"
#include "ios/web/public/web_state/web_state_observer.h" #include "ios/web/public/web_state/web_state_observer.h"
#import "ios/web/web_state/ui/crw_js_injector.h"
#import "ios/web/web_state/ui/crw_web_controller.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/ui/wk_web_view_configuration_provider.h"
#import "ios/web/web_state/web_state_impl.h" #import "ios/web/web_state/web_state_impl.h"
...@@ -207,7 +208,7 @@ id WebTestWithWebState::ExecuteJavaScript(NSString* script) { ...@@ -207,7 +208,7 @@ id WebTestWithWebState::ExecuteJavaScript(NSString* script) {
__block id execution_result = nil; __block id execution_result = nil;
__block bool execution_completed = false; __block bool execution_completed = false;
SCOPED_TRACE(base::SysNSStringToUTF8(script)); SCOPED_TRACE(base::SysNSStringToUTF8(script));
[GetWebController(web_state()) [GetWebController(web_state()).jsInjector
executeJavaScript:script executeJavaScript:script
completionHandler:^(id result, NSError* error) { completionHandler:^(id result, NSError* error) {
// Most of executed JS does not return the result, and there is no need // Most of executed JS does not return the result, and there is no need
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h" #import "base/test/ios/wait_util.h"
#import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h" #import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/web_state/ui/crw_js_injector.h"
#import "ios/web/web_state/ui/crw_web_controller.h" #import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/ui/crw_web_view_proxy_impl.h" #import "ios/web/web_state/ui/crw_web_view_proxy_impl.h"
#import "ios/web/web_state/web_state_impl.h" #import "ios/web/web_state/web_state_impl.h"
...@@ -184,12 +185,12 @@ bool RunActionOnWebViewElementWithScript(web::WebState* web_state, ...@@ -184,12 +185,12 @@ bool RunActionOnWebViewElementWithScript(web::WebState* web_state,
// |executeUserJavaScript:completionHandler:| is no-op for app-specific URLs, // |executeUserJavaScript:completionHandler:| is no-op for app-specific URLs,
// so simulate a user gesture by calling TouchTracking method. // so simulate a user gesture by calling TouchTracking method.
[web_controller touched:YES]; [web_controller touched:YES];
[web_controller executeJavaScript:script [web_controller.jsInjector executeJavaScript:script
completionHandler:^(id result, NSError* error) { completionHandler:^(id result, NSError* error) {
did_complete = true; did_complete = true;
element_found = [result boolValue]; element_found = [result boolValue];
block_error = [error copy]; block_error = [error copy];
}]; }];
bool js_finished = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ bool js_finished = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
return did_complete; return did_complete;
......
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
- (void)executeJavaScript:(NSString*)script - (void)executeJavaScript:(NSString*)script
completionHandler:(void (^)(id, NSError*))completionHandler; completionHandler:(void (^)(id, NSError*))completionHandler;
// Asynchronously executes |javaScript| in the main frame's context,
// registering user interaction. For security reasons, some implementations may
// reject the request if the page has some elevated privileges.
- (void)executeUserJavaScript:(NSString*)script
completionHandler:(void (^)(id, NSError*))completionHandler;
// Checks to see if the script for a class has been injected into the // Checks to see if the script for a class has been injected into the
// current page already. // current page already.
- (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass; - (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#endif #endif
@implementation CRWJSInjectionReceiver { @implementation CRWJSInjectionReceiver {
// Used to evaluate JavaScripts. // Used to evaluate JavaScript.
__weak id<CRWJSInjectionEvaluator> _evaluator; __weak id<CRWJSInjectionEvaluator> _evaluator;
// Map from a CRWJSInjectionManager class to its instance created for this // Map from a CRWJSInjectionManager class to its instance created for this
...@@ -39,6 +39,11 @@ ...@@ -39,6 +39,11 @@
[_evaluator executeJavaScript:script completionHandler:completionHandler]; [_evaluator executeJavaScript:script completionHandler:completionHandler];
} }
- (void)executeUserJavaScript:(NSString*)script
completionHandler:(void (^)(id, NSError*))completionHandler {
[_evaluator executeUserJavaScript:script completionHandler:completionHandler];
}
- (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass { - (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass {
return [_evaluator scriptHasBeenInjectedForClass:injectionManagerClass]; return [_evaluator scriptHasBeenInjectedForClass:injectionManagerClass];
} }
......
...@@ -44,6 +44,8 @@ source_set("ui") { ...@@ -44,6 +44,8 @@ source_set("ui") {
] ]
sources = [ sources = [
"crw_js_injector.h",
"crw_js_injector.mm",
"crw_swipe_recognizer_provider.h", "crw_swipe_recognizer_provider.h",
"crw_touch_tracking_recognizer.h", "crw_touch_tracking_recognizer.h",
"crw_touch_tracking_recognizer.mm", "crw_touch_tracking_recognizer.mm",
......
// Copyright 2019 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_WEB_STATE_UI_CRW_JS_INJECTOR_H_
#define IOS_WEB_WEB_STATE_UI_CRW_JS_INJECTOR_H_
#import <UIKit/UIKit.h>
#import "ios/web/public/web_state/js/crw_js_injection_evaluator.h"
#include "url/gurl.h"
@class CRWJSInjectionReceiver;
@class WKWebView;
@class CRWJSInjector;
@protocol CRWJSInjectorDelegate <NSObject>
// Tells delegate that user script is about to be executed.
- (void)willExecuteUserScriptForJSInjector:(CRWJSInjector*)injector;
// Returns the last committed URL by the delegate.
- (GURL)lastCommittedURLForJSInjector:(CRWJSInjector*)injector;
@end
// TODO(crbug.com/954137): This class is responsible for both "injection" and
// "execution" and probably should be split into separate classes (f.e.
// CRWJSExecutor and CRWLegacyInjector) when we get to the next phase of
// refactoring. CRWLegacyInjector would be removed at some point, while
// CRWJSExecutor would always stay around to support omnibox.
@interface CRWJSInjector : NSObject <CRWJSInjectionEvaluator>
@property(strong, nonatomic, readonly)
CRWJSInjectionReceiver* JSInjectionReceiver;
// Contains a web view, if one is associated.
@property(weak, nonatomic) WKWebView* webView;
// Designated initializer. Initializes with |delegate|.
- (instancetype)initWithDelegate:(id<CRWJSInjectorDelegate>)delegate;
// Resets list of all scripts injected with |injectScript|. Affects only results
// returned by |scriptHasBeenInjectedForClass|.
- (void)resetInjectedScriptSet;
// Injects windowId in the web page.
- (void)injectWindowID;
@end
#endif // IOS_WEB_WEB_STATE_UI_CRW_JS_INJECTOR_H_
// Copyright 2019 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/crw_js_injector.h"
#import <WebKit/WebKit.h>
#include "base/logging.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state/js/crw_js_injection_manager.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#import "ios/web/web_state/js/crw_js_window_id_manager.h"
#import "ios/web/web_state/ui/web_view_js_utils.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CRWJSInjector () {
__weak id<CRWJSInjectorDelegate> _delegate;
// Script manager for setting the windowID.
CRWJSWindowIDManager* _windowIDJSManager;
// A set of script managers whose scripts have been injected into the current
// page.
NSMutableSet* _injectedScriptManagers;
}
@end
@implementation CRWJSInjector
- (instancetype)initWithDelegate:(id<CRWJSInjectorDelegate>)delegate {
self = [super init];
if (self) {
_delegate = delegate;
_JSInjectionReceiver =
[[CRWJSInjectionReceiver alloc] initWithEvaluator:self];
_injectedScriptManagers = [[NSMutableSet alloc] init];
}
return self;
}
- (void)resetInjectedScriptSet {
[_injectedScriptManagers removeAllObjects];
}
- (void)setWebView:(WKWebView*)webView {
_webView = webView;
if (webView) {
_windowIDJSManager = [[CRWJSWindowIDManager alloc] initWithWebView:webView];
} else {
_windowIDJSManager = nil;
}
}
- (void)injectWindowID {
[_windowIDJSManager inject];
}
#pragma mark - CRWJSInjectionEvaluator
- (void)executeJavaScript:(NSString*)script
completionHandler:(void (^)(id, NSError*))completionHandler {
NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script];
web::ExecuteJavaScript(self.webView, safeScript, completionHandler);
}
- (void)executeUserJavaScript:(NSString*)script
completionHandler:(void (^)(id, NSError*))completionHandler {
// For security reasons, executing JavaScript on pages with app-specific URLs
// is not allowed, because those pages may have elevated privileges.
GURL lastCommittedURL = [_delegate lastCommittedURLForJSInjector:self];
if (web::GetWebClient()->IsAppSpecificURL(lastCommittedURL)) {
if (completionHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError* error = [[NSError alloc]
initWithDomain:web::kJSEvaluationErrorDomain
code:web::JS_EVALUATION_ERROR_CODE_REJECTED
userInfo:nil];
completionHandler(nil, error);
});
}
return;
}
[_delegate willExecuteUserScriptForJSInjector:self];
[self executeJavaScript:script completionHandler:completionHandler];
}
- (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass {
return [_injectedScriptManagers containsObject:injectionManagerClass];
}
- (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
DCHECK(script.length);
// Script execution is an asynchronous operation which may pass sensitive
// data to the page. executeJavaScript:completionHandler makes sure that
// receiver page did not change by checking its window id.
// |[self.webView executeJavaScript:completionHandler:]| is not used here
// because it does not check that page is the same.
[self executeJavaScript:script completionHandler:nil];
[_injectedScriptManagers addObject:JSInjectionManagerClass];
}
#pragma mark - JavaScript Helpers (Private)
// Returns a new script which wraps |script| with windowID check so |script| is
// not evaluated on windowID mismatch.
- (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
return [NSString
stringWithFormat:kTemplate, [_windowIDJSManager windowID], script];
}
@end
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "ios/web/navigation/crw_session_controller.h" #import "ios/web/navigation/crw_session_controller.h"
#import "ios/web/public/web_state/js/crw_js_injection_evaluator.h"
#include "ios/web/public/web_state/url_verification_constants.h" #include "ios/web/public/web_state/url_verification_constants.h"
#import "ios/web/public/web_state/web_state.h" #import "ios/web/public/web_state/web_state.h"
#import "ios/web/web_state/ui/crw_touch_tracking_recognizer.h" #import "ios/web/web_state/ui/crw_touch_tracking_recognizer.h"
...@@ -21,7 +20,7 @@ enum class WKNavigationState; ...@@ -21,7 +20,7 @@ enum class WKNavigationState;
} // namespace web } // namespace web
@class CRWJSInjectionReceiver; @class CRWJSInjector;
@protocol CRWNativeContent; @protocol CRWNativeContent;
@protocol CRWNativeContentProvider; @protocol CRWNativeContentProvider;
@protocol CRWSwipeRecognizerProvider; @protocol CRWSwipeRecognizerProvider;
...@@ -41,9 +40,8 @@ class WebStateImpl; ...@@ -41,9 +40,8 @@ class WebStateImpl;
// web view. // web view.
// This is an abstract class which must not be instantiated directly. // This is an abstract class which must not be instantiated directly.
// TODO(stuartmorgan): Move all of the navigation APIs out of this class. // TODO(stuartmorgan): Move all of the navigation APIs out of this class.
@interface CRWWebController : NSObject <CRWJSInjectionEvaluator, @interface CRWWebController
CRWSessionControllerDelegate, : NSObject <CRWSessionControllerDelegate, CRWTouchTrackingDelegate>
CRWTouchTrackingDelegate>
// Whether or not a UIWebView is allowed to exist in this CRWWebController. // Whether or not a UIWebView is allowed to exist in this CRWWebController.
// Defaults to NO; this should be enabled before attempting to access the view. // Defaults to NO; this should be enabled before attempting to access the view.
...@@ -86,9 +84,8 @@ class WebStateImpl; ...@@ -86,9 +84,8 @@ class WebStateImpl;
// back-forward list navigations. // back-forward list navigations.
@property(nonatomic) BOOL allowsBackForwardNavigationGestures; @property(nonatomic) BOOL allowsBackForwardNavigationGestures;
// The receiver of JavaScripts. // JavaScript injector.
@property(nonatomic, strong, readonly) @property(nonatomic, strong, readonly) CRWJSInjector* jsInjector;
CRWJSInjectionReceiver* jsInjectionReceiver;
// Whether the WebController should attempt to keep the render process alive. // Whether the WebController should attempt to keep the render process alive.
@property(nonatomic, assign, getter=shouldKeepRenderProcessAlive) @property(nonatomic, assign, getter=shouldKeepRenderProcessAlive)
...@@ -166,13 +163,6 @@ class WebStateImpl; ...@@ -166,13 +163,6 @@ class WebStateImpl;
// Stops loading the page. // Stops loading the page.
- (void)stopLoading; - (void)stopLoading;
// Executes |script| in the web view, registering user interaction.
// |result| will be backed up by different classes depending on resulting JS
// type: NSString (string), NSNumber (number or boolean), NSDictionary (object),
// NSArray (array), NSNull (null), NSDate (Date), nil (undefined).
- (void)executeUserJavaScript:(NSString*)script
completionHandler:(void (^)(id result, NSError*))completion;
// Requires that the next load rebuild the web view. This is expensive, and // Requires that the next load rebuild the web view. This is expensive, and
// should be used only in the case where something has changed that the web view // should be used only in the case where something has changed that the web view
// only checks on creation, such that the whole object needs to be rebuilt. // only checks on creation, such that the whole object needs to be rebuilt.
......
This diff is collapsed.
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include "ios/web/test/test_url_constants.h" #include "ios/web/test/test_url_constants.h"
#import "ios/web/test/web_test_with_web_controller.h" #import "ios/web/test/web_test_with_web_controller.h"
#import "ios/web/test/wk_web_view_crash_utils.h" #import "ios/web/test/wk_web_view_crash_utils.h"
#import "ios/web/web_state/ui/crw_js_injector.h"
#import "ios/web/web_state/ui/crw_web_controller.h" #import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/ui/crw_web_controller_container_view.h" #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
#import "ios/web/web_state/ui/web_view_js_utils.h" #import "ios/web/web_state/ui/web_view_js_utils.h"
...@@ -1224,7 +1225,7 @@ class ScriptExecutionTest : public ProgrammaticWebTestWithWebController { ...@@ -1224,7 +1225,7 @@ class ScriptExecutionTest : public ProgrammaticWebTestWithWebController {
__block id script_result = nil; __block id script_result = nil;
__block NSError* script_error = nil; __block NSError* script_error = nil;
__block bool script_executed = false; __block bool script_executed = false;
[web_controller() [web_controller().jsInjector
executeUserJavaScript:java_script executeUserJavaScript:java_script
completionHandler:^(id local_result, NSError* local_error) { completionHandler:^(id local_result, NSError* local_error) {
script_result = local_result; script_result = local_result;
...@@ -1272,7 +1273,7 @@ TEST_P(ScriptExecutionTest, UserScriptOnAppSpecificPage) { ...@@ -1272,7 +1273,7 @@ TEST_P(ScriptExecutionTest, UserScriptOnAppSpecificPage) {
EXPECT_FALSE(ExecuteUserJavaScript(@"window.w = 0;", &error)); EXPECT_FALSE(ExecuteUserJavaScript(@"window.w = 0;", &error));
ASSERT_TRUE(error); ASSERT_TRUE(error);
EXPECT_NSEQ(kJSEvaluationErrorDomain, error.domain); EXPECT_NSEQ(kJSEvaluationErrorDomain, error.domain);
EXPECT_EQ(JS_EVALUATION_ERROR_CODE_NO_WEB_VIEW, error.code); EXPECT_EQ(JS_EVALUATION_ERROR_CODE_REJECTED, error.code);
EXPECT_FALSE(ExecuteJavaScript(@"window.w")); EXPECT_FALSE(ExecuteJavaScript(@"window.w"));
} }
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include "ios/web/public/webui/web_ui_ios_controller.h" #include "ios/web/public/webui/web_ui_ios_controller.h"
#include "ios/web/web_state/global_web_state_event_tracker.h" #include "ios/web/web_state/global_web_state_event_tracker.h"
#import "ios/web/web_state/session_certificate_policy_cache_impl.h" #import "ios/web/web_state/session_certificate_policy_cache_impl.h"
#import "ios/web/web_state/ui/crw_js_injector.h"
#import "ios/web/web_state/ui/crw_web_controller.h" #import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/ui/crw_web_controller_container_view.h" #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
#import "ios/web/web_state/ui/crw_web_view_navigation_proxy.h" #import "ios/web/web_state/ui/crw_web_view_navigation_proxy.h"
...@@ -647,32 +648,33 @@ void WebStateImpl::LoadData(NSData* data, ...@@ -647,32 +648,33 @@ void WebStateImpl::LoadData(NSData* data,
} }
CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const { CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const {
return [web_controller_ jsInjectionReceiver]; return [web_controller_.jsInjector JSInjectionReceiver];
} }
void WebStateImpl::ExecuteJavaScript(const base::string16& javascript) { void WebStateImpl::ExecuteJavaScript(const base::string16& javascript) {
[web_controller_ executeJavaScript:base::SysUTF16ToNSString(javascript) [web_controller_.jsInjector
completionHandler:nil]; executeJavaScript:base::SysUTF16ToNSString(javascript)
completionHandler:nil];
} }
void WebStateImpl::ExecuteJavaScript(const base::string16& javascript, void WebStateImpl::ExecuteJavaScript(const base::string16& javascript,
JavaScriptResultCallback callback) { JavaScriptResultCallback callback) {
__block JavaScriptResultCallback stack_callback = std::move(callback); __block JavaScriptResultCallback stack_callback = std::move(callback);
[web_controller_ executeJavaScript:base::SysUTF16ToNSString(javascript) [web_controller_.jsInjector
completionHandler:^(id value, NSError* error) { executeJavaScript:base::SysUTF16ToNSString(javascript)
if (error) { completionHandler:^(id value, NSError* error) {
DLOG(WARNING) if (error) {
<< "Script execution has failed: " DLOG(WARNING) << "Script execution has failed: "
<< base::SysNSStringToUTF16( << base::SysNSStringToUTF16(
error.userInfo[NSLocalizedDescriptionKey]); error.userInfo[NSLocalizedDescriptionKey]);
} }
std::move(stack_callback) std::move(stack_callback).Run(ValueResultFromWKResult(value).get());
.Run(ValueResultFromWKResult(value).get()); }];
}];
} }
void WebStateImpl::ExecuteUserJavaScript(NSString* javaScript) { void WebStateImpl::ExecuteUserJavaScript(NSString* javaScript) {
[web_controller_ executeUserJavaScript:javaScript completionHandler:nil]; [web_controller_.jsInjector executeUserJavaScript:javaScript
completionHandler:nil];
} }
const std::string& WebStateImpl::GetContentsMimeType() const { const std::string& WebStateImpl::GetContentsMimeType() const {
......
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