forked from crosswalk-project/chromium-crosswalk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for POST request with bodies on WKWebView.
These requests are now handled through a javascript hack to work around this webkit bug: https://bugs.webkit.org/show_bug.cgi?id=145410. BUG=489692 Review URL: https://codereview.chromium.org/1375023002 Cr-Commit-Position: refs/heads/master@{#363256} (cherry picked from commit 8d13d52) Review URL: https://codereview.chromium.org/1516303002 . Cr-Commit-Position: refs/branch-heads/2564@{crosswalk-project#326} Cr-Branched-From: 1283eca-refs/heads/master@{#359700}
- Loading branch information
Showing
9 changed files
with
447 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright 2015 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_JS_CRW_JS_POST_REQUEST_LOADER_H_ | ||
#define IOS_WEB_WEB_STATE_JS_CRW_JS_POST_REQUEST_LOADER_H_ | ||
|
||
#import <WebKit/WebKit.h> | ||
|
||
@class CRWWKScriptMessageRouter; | ||
|
||
// Class to load POST requests in a provided web view via JavaScript. | ||
@interface CRWJSPOSTRequestLoader : NSObject | ||
|
||
// Asynchronously loads a POST |request| in provided |webView|. | ||
// It temporarily installs JavaScript message routers with |messageRouter| to | ||
// handle HTTP errors. The |completionHandler| is called once the request has | ||
// been executed. In case of successful request, the passed error is nil. | ||
// The |completionHandler| must not be null. The |messageRouter| and |webView| | ||
// must not be nil. The |request| must be a POST request. | ||
- (void)loadPOSTRequest:(NSURLRequest*)request | ||
inWebView:(WKWebView*)webView | ||
messageRouter:(CRWWKScriptMessageRouter*)messageRouter | ||
completionHandler:(void (^)(NSError*))completionHandler; | ||
|
||
@end | ||
|
||
#endif // IOS_WEB_WEB_STATE_JS_CRW_JS_POST_REQUEST_LOADER_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// Copyright 2015 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/js/crw_js_post_request_loader.h" | ||
|
||
#include "base/json/string_escape.h" | ||
#import "base/mac/scoped_nsobject.h" | ||
#import "base/strings/sys_string_conversions.h" | ||
#import "ios/web/web_state/js/page_script_util.h" | ||
#import "ios/web/web_state/ui/crw_wk_script_message_router.h" | ||
|
||
namespace { | ||
|
||
// Escapes characters and encloses given string in quotes for use in JavaScript. | ||
NSString* EscapeAndQuoteStringForJavaScript(NSString* unescapedString) { | ||
std::string string = base::SysNSStringToUTF8(unescapedString); | ||
return base::SysUTF8ToNSString(base::GetQuotedJSONString(string)); | ||
} | ||
|
||
// JavaScript message handler name installed in WKWebView for request errors. | ||
NSString* const kErrorHandlerName = @"POSTErrorHandler"; | ||
|
||
// JavaScript message handler name installed in WKWebView for successful | ||
// request completion. | ||
NSString* const kSuccessHandlerName = @"POSTSuccessHandler"; | ||
|
||
} // namespace | ||
|
||
@interface CRWJSPOSTRequestLoader () { | ||
base::scoped_nsobject<NSString> _requestScript; | ||
} | ||
|
||
// JavaScript used to execute POST requests. Lazily instantiated. | ||
@property(nonatomic, copy, readonly) NSString* requestScript; | ||
|
||
// Handler for UIApplicationDidReceiveMemoryWarningNotification. | ||
- (void)handleMemoryWarning; | ||
|
||
// Forms a JavaScript method call to |requestScript| that executes given | ||
// |request|. | ||
- (NSString*)scriptToExecutePOSTRequest:(NSURLRequest*)request; | ||
|
||
// Converts a dictionary of HTTP request headers to a JavaScript object. | ||
- (NSString*)JSONForJavaScriptFromRequestHeaders:(NSDictionary*)headers; | ||
|
||
@end | ||
|
||
@implementation CRWJSPOSTRequestLoader | ||
|
||
- (instancetype)init { | ||
self = [super init]; | ||
if (self) { | ||
[[NSNotificationCenter defaultCenter] | ||
addObserver:self | ||
selector:@selector(handleMemoryWarning) | ||
name:UIApplicationDidReceiveMemoryWarningNotification | ||
object:nil]; | ||
} | ||
return self; | ||
} | ||
|
||
- (void)dealloc { | ||
[[NSNotificationCenter defaultCenter] removeObserver:self]; | ||
[super dealloc]; | ||
} | ||
|
||
- (NSString*)requestScript { | ||
if (!_requestScript) { | ||
_requestScript.reset([web::GetPageScript(@"post_request") copy]); | ||
} | ||
return _requestScript; | ||
} | ||
|
||
- (void)loadPOSTRequest:(NSURLRequest*)request | ||
inWebView:(WKWebView*)webView | ||
messageRouter:(CRWWKScriptMessageRouter*)messageRouter | ||
completionHandler:(void (^)(NSError*))completionHandler { | ||
DCHECK([request.HTTPMethod isEqualToString:@"POST"]); | ||
DCHECK(webView); | ||
DCHECK(messageRouter); | ||
DCHECK(completionHandler); | ||
|
||
// Install error handling and success routers. | ||
[messageRouter setScriptMessageHandler:^(WKScriptMessage* message) { | ||
// Cleaning up script handlers. | ||
[messageRouter removeScriptMessageHandlerForName:kErrorHandlerName | ||
webView:webView]; | ||
[messageRouter removeScriptMessageHandlerForName:kSuccessHandlerName | ||
webView:webView]; | ||
completionHandler(nil); | ||
} | ||
name:kSuccessHandlerName | ||
webView:webView]; | ||
|
||
[messageRouter setScriptMessageHandler:^(WKScriptMessage* message) { | ||
NSNumber* statusCode = message.body; | ||
NSError* error = [NSError errorWithDomain:NSURLErrorDomain | ||
code:statusCode.integerValue | ||
userInfo:nil]; | ||
[messageRouter removeScriptMessageHandlerForName:kErrorHandlerName | ||
webView:webView]; | ||
[messageRouter removeScriptMessageHandlerForName:kSuccessHandlerName | ||
webView:webView]; | ||
completionHandler(error); | ||
} | ||
name:kErrorHandlerName | ||
webView:webView]; | ||
|
||
NSString* HTML = | ||
[NSString stringWithFormat:@"<html><script>%@%@</script></html>", | ||
self.requestScript, | ||
[self scriptToExecutePOSTRequest:request]]; | ||
[webView loadHTMLString:HTML baseURL:request.URL]; | ||
} | ||
|
||
#pragma mark - Private methods. | ||
|
||
- (void)handleMemoryWarning { | ||
// Request script can be recreated from file at any moment. | ||
_requestScript.reset(); | ||
} | ||
|
||
- (NSString*)scriptToExecutePOSTRequest:(NSURLRequest*)request { | ||
NSDictionary* headers = [request allHTTPHeaderFields]; | ||
NSString* headerString = [self JSONForJavaScriptFromRequestHeaders:headers]; | ||
NSString* URLString = [[request URL] absoluteString]; | ||
NSString* contentType = headers[@"Content-Type"]; | ||
NSString* base64Data = [[request HTTPBody] base64EncodedStringWithOptions:0]; | ||
|
||
// Here |headerString| is already properly escaped when returned from | ||
// -JSONForJavaScriptFromRequestHeaders:. | ||
return | ||
[NSString stringWithFormat: | ||
@"__crPostRequestWorkaround.runPostRequest(%@, %@, %@, %@)", | ||
EscapeAndQuoteStringForJavaScript(URLString), headerString, | ||
EscapeAndQuoteStringForJavaScript(base64Data), | ||
EscapeAndQuoteStringForJavaScript(contentType)]; | ||
} | ||
|
||
- (NSString*)JSONForJavaScriptFromRequestHeaders:(NSDictionary*)headers { | ||
if (headers) { | ||
NSData* headerData = | ||
[NSJSONSerialization dataWithJSONObject:headers options:0 error:nil]; | ||
if (headerData) { | ||
// This string is properly escaped by NSJSONSerialization. It needs to | ||
// have no quotes since JavaScripts takes this parameter as an | ||
// Object<string, string>. | ||
return [[[NSString alloc] initWithData:headerData | ||
encoding:NSUTF8StringEncoding] autorelease]; | ||
} | ||
} | ||
return @"{}"; | ||
} | ||
|
||
@end |
105 changes: 105 additions & 0 deletions
105
ios/web/web_state/js/crw_js_post_request_loader_unittest.mm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Copyright 2015 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/js/crw_js_post_request_loader.h" | ||
|
||
#import <WebKit/WebKit.h> | ||
|
||
#import "base/mac/foundation_util.h" | ||
#import "base/mac/scoped_nsobject.h" | ||
#include "base/strings/sys_string_conversions.h" | ||
#import "base/test/ios/wait_util.h" | ||
#include "ios/web/public/test/web_test_util.h" | ||
#import "ios/web/public/web_view_creation_util.h" | ||
#import "ios/web/test/web_test.h" | ||
#import "ios/web/web_state/ui/crw_wk_script_message_router.h" | ||
#import "testing/gtest_mac.h" | ||
#import "third_party/ocmock/OCMock/OCMock.h" | ||
|
||
namespace base { | ||
namespace { | ||
|
||
typedef web::WebTest CRWJSPOSTRequestLoaderTest; | ||
|
||
// This script takes a JavaScript blob and converts it to a base64-encoded | ||
// string asynchronously, then is sent to XHRSendHandler message handler. | ||
NSString* const kBlobToBase64StringScript = | ||
@"var blobToBase64 = function(x) {" | ||
" var reader = new window.FileReader();" | ||
" reader.readAsDataURL(x);" | ||
" reader.onloadend = function() {" | ||
" base64data = reader.result;" | ||
" window.webkit.messageHandlers.XHRSendHandler.postMessage(base64data);" | ||
" };" | ||
"};"; | ||
|
||
// Tests that the POST request is correctly executed through XMLHttpRequest. | ||
TEST_F(CRWJSPOSTRequestLoaderTest, LoadsCorrectHTML) { | ||
CR_TEST_REQUIRES_WK_WEB_VIEW(); | ||
|
||
// Set up necessary objects. | ||
scoped_nsobject<CRWJSPOSTRequestLoader> loader( | ||
[[CRWJSPOSTRequestLoader alloc] init]); | ||
scoped_nsobject<WKWebView> web_view( | ||
web::CreateWKWebView(CGRectZero, GetBrowserState())); | ||
WKUserContentController* contentController = | ||
web_view.get().configuration.userContentController; | ||
scoped_nsobject<CRWWKScriptMessageRouter> messageRouter( | ||
[[CRWWKScriptMessageRouter alloc] | ||
initWithUserContentController:contentController]); | ||
|
||
// Override XMLHttpRequest.send() to call kBlobToBase64StringScript. | ||
__block BOOL overrideSuccessfull = NO; | ||
NSString* JS = [kBlobToBase64StringScript stringByAppendingString:@";\ | ||
XMLHttpRequest.prototype.send = function(x) { blobToBase64(x); };"]; | ||
[web_view evaluateJavaScript:JS | ||
completionHandler:^(id, NSError*) { | ||
overrideSuccessfull = YES; | ||
}]; | ||
base::test::ios::WaitUntilCondition(^bool { | ||
return overrideSuccessfull; | ||
}); | ||
|
||
NSString* post_body = @"123"; | ||
|
||
// Adds XHRSendHandler handler that checks that the POST request body is | ||
// correct. Sets |complete| flag upon completion. | ||
__block BOOL complete = NO; | ||
void (^XHRSendHandler)(WKScriptMessage*) = ^(WKScriptMessage* message) { | ||
NSString* body = base::mac::ObjCCast<NSString>(message.body); | ||
NSArray* components = [body componentsSeparatedByString:@","]; | ||
EXPECT_EQ(components.count, 2u); | ||
EXPECT_NSEQ(components[0], @"data:;base64"); | ||
NSData* expectedData = [post_body dataUsingEncoding:NSUTF8StringEncoding]; | ||
EXPECT_NSEQ(components[1], [expectedData base64EncodedStringWithOptions:0]); | ||
complete = YES; | ||
}; | ||
|
||
[messageRouter setScriptMessageHandler:XHRSendHandler | ||
name:@"XHRSendHandler" | ||
webView:web_view]; | ||
|
||
// Construct and perform the POST request. | ||
NSURL* url = [NSURL URLWithString:@"http://google.com"]; | ||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; | ||
request.HTTPMethod = @"POST"; | ||
request.HTTPBody = [post_body dataUsingEncoding:NSUTF8StringEncoding]; | ||
[loader loadPOSTRequest:request | ||
inWebView:web_view | ||
messageRouter:messageRouter | ||
completionHandler:^(NSError*){ | ||
}]; | ||
|
||
// Wait until the JavaScript message handler is called. | ||
base::test::ios::WaitUntilCondition(^bool { | ||
return complete; | ||
}); | ||
|
||
// Clean up installed script handler. | ||
[messageRouter removeScriptMessageHandlerForName:@"XHRSendHandler" | ||
webView:web_view]; | ||
} | ||
|
||
} // namespace | ||
} // namespace base |
Oops, something went wrong.