Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(726)

Unified Diff: ios/web/web_state/ui/crw_wk_web_view_web_controller.mm

Issue 1375023002: Adds support for POST request with bodies on WKWebView. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Formatting Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/web/web_state/ui/crw_wk_web_view_web_controller.mm
diff --git a/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm b/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm
index 368587eab562249bab292466ff34a608391942d3..55f570bfac61c65ff308faca7d1dea0864758bc4 100644
--- a/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm
+++ b/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm
@@ -9,6 +9,7 @@
#include "base/ios/ios_util.h"
#include "base/ios/weak_nsobject.h"
#include "base/json/json_reader.h"
+#include "base/json/string_escape.h"
#import "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/strings/sys_string_conversions.h"
@@ -53,9 +54,62 @@ NSString* GetRefererFromNavigationAction(WKNavigationAction* action) {
return [action.request valueForHTTPHeaderField:@"Referer"];
}
+// 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));
stuartmorgan 2015/10/01 23:36:27 The round-tripping of what's going to be a really
stkhapugin 2015/10/08 16:57:24 I can't find a way to do it directly on NSString.
+}
+
NSString* const kScriptMessageName = @"crwebinvoke";
NSString* const kScriptImmediateName = @"crwebinvokeimmediate";
+// JavaScript template to do a POST request using an XMLHttpRequest.
+// It takes three arguments (in order):
+// * The quoted and escaped URL to send a POST request to.
+// * The HTTP headers of the request. They should be written as valid JavaScript
+// statements, adding headers to the XMLHttpRequest variable named 'req'
+// (e.g. 'req.setRequestHeader("Foo", "Bar");').
+// * The base64 string of POST request body.
+// * Content-Type string
+NSString* const kPostRequestTemplate =
+ @"<html><script>"
stuartmorgan 2015/10/01 23:36:27 I'm with Eugene; this is sizable chunk of JS, and
stkhapugin 2015/10/08 16:57:24 Done.
+ "function b64ToBlob(b64Data, contentType) {"
+ " contentType = contentType || '';"
+ " var sliceSize = 512;"
+ " var byteCharacters = b64Data;"
+ " var byteArrays = [];"
+ " for (var offset = 0; offset < byteCharacters.length; offset += "
+ "sliceSize) {"
+ " var slice = byteCharacters.slice(offset, offset + sliceSize);"
+ " var byteNumbers = new Array(slice.length);"
+ " for (var i = 0; i < slice.length; i++) {"
+ " byteNumbers[i] = slice.charCodeAt(i);"
+ " }"
+ " var byteArray = new Uint8Array(byteNumbers);"
+ " byteArrays.push(byteArray);"
+ " }"
+ " var blob = new Blob(byteArrays, {type: contentType});"
+ " return blob;"
+ "}"
+ " function createAndSendPostRequest() {"
+ " var req = new XMLHttpRequest();"
+ " req.open(\"POST\", %@, false);"
+ " %@" //< This sets request headers.
+ " var blob = b64ToBlob(atob(%@), %@);" //< base64 string and
+ // content type
+ " req.send(blob);"
+ " if (req.status != 200) {"
+ " throw req.status;"
+ " }"
+ " return req.responseText;"
+ " }"
+ " "
+ " document.open();"
+ " document.write(createAndSendPostRequest());"
+ " document.close();"
+
+ "</script></html>";
+
// Utility functions for storing the source of NSErrors received by WKWebViews:
// - Errors received by |-webView:didFailProvisionalNavigation:withError:| are
// recorded using WKWebViewErrorSource::PROVISIONAL_LOAD. These should be
@@ -169,6 +223,20 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
@property(nonatomic, readonly) int certGroupID;
#endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
+// Constructs an HTML page that executes the request through JavaScript and
+// replaces document with the result.
+- (NSString*)HTMLForPOSTRequest:(NSURLRequest*)request;
+
+// Loads POST request with body in |_wkWebView| by constructing an HTML page
+// that executes the request through javascript and replaces document with the
+// result.
+// Note that this approach includes multiple body encodings and decodings, plus
+// the data is passed to |_wkWebView| on main thread, hence the performance is
+// expected to be low.
+// This is necessary because WKWebView ignores POST request body.
+// Workaround for https://bugs.webkit.org/show_bug.cgi?id=145410
+- (void)loadPOSTRequestWithBody:(NSMutableURLRequest*)request;
+
// Returns the WKWebViewConfigurationProvider associated with the web
// controller's BrowserState.
- (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider;
@@ -448,6 +516,33 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
[_wkWebView loadRequest:request];
}
+- (NSString*)HTMLForPOSTRequest:(NSURLRequest*)request {
Eugene But (OOO till 7-30) 2015/09/30 15:45:16 NIT: Please move these methods before "webViewConf
stkhapugin 2015/10/08 16:57:24 Done.
+ NSString* base64Data = [[request HTTPBody] base64EncodedStringWithOptions:0];
+ NSString* originURL = [[request URL] absoluteString];
+ NSString* contentType = nil;
+ NSMutableString* headerString = [NSMutableString string];
+ for (NSString* headerField in [[request allHTTPHeaderFields] allKeys]) {
+ NSString* value = [request allHTTPHeaderFields][headerField];
+ if ([headerField isEqualToString:@"Content-Type"]) {
+ contentType = value;
+ }
+ [headerString appendFormat:@"req.setRequestHeader(%@, %@);",
+ EscapeAndQuoteStringForJavaScript(headerField),
+ EscapeAndQuoteStringForJavaScript(value)];
+ }
+
+ return [NSString stringWithFormat:kPostRequestTemplate,
+ EscapeAndQuoteStringForJavaScript(originURL),
+ headerString,
+ EscapeAndQuoteStringForJavaScript(base64Data),
+ EscapeAndQuoteStringForJavaScript(contentType)];
+}
+
+- (void)loadPOSTRequestWithBody:(NSMutableURLRequest*)request {
+ NSString* HTML = [self HTMLForPOSTRequest:request];
+ [_wkWebView loadHTMLString:HTML baseURL:request.URL];
+}
+
- (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
[_wkWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
}
@@ -477,25 +572,36 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
- (void)loadRequestForCurrentNavigationItem {
DCHECK(self.webView && !self.nativeController);
+ DCHECK([self currentSessionEntry]);
+
+ web::WKBackForwardListItemHolder* holder =
+ [self currentBackForwardListItemHolder];
+ BOOL isFormResubmission =
+ (holder->navigation_type() == WKNavigationTypeFormResubmitted ||
+ holder->navigation_type() == WKNavigationTypeFormSubmitted);
+ web::NavigationItemImpl* currentItem =
+ [self currentSessionEntry].navigationItemImpl;
+ NSData* POSTData = currentItem->GetPostData();
+ NSMutableURLRequest* request = [self requestForCurrentNavigationItem];
ProceduralBlock defaultNavigationBlock = ^{
[self registerLoadRequest:[self currentNavigationURL]
referrer:[self currentSessionEntryReferrer]
transition:[self currentTransition]];
- [self loadRequest:[self requestForCurrentNavigationItem]];
+ [self loadRequest:request];
};
- // If there is no corresponding WKBackForwardListItem, or the item is not in
- // the current WKWebView's back-forward list, navigating using WKWebView API
- // is not possible. In this case, fall back to the default navigation
- // mechanism.
- web::WKBackForwardListItemHolder* holder =
- [self currentBackForwardListItemHolder];
- if (!holder->back_forward_list_item() ||
- ![self isBackForwardListItemValid:holder->back_forward_list_item()]) {
- defaultNavigationBlock();
- return;
- }
+ // If the request has POST data and is not a form resubmission, configure and
Eugene But (OOO till 7-30) 2015/09/30 15:45:16 This comment should be at line 621, not here.
stkhapugin 2015/10/08 16:57:24 Done.
+ // run the POST request.
+ ProceduralBlock POSTBlock = ^{
+ [request setHTTPMethod:@"POST"];
+ [request setHTTPBody:POSTData];
+ [request setAllHTTPHeaderFields:[self currentHTTPHeaders]];
+ [self registerLoadRequest:[self currentNavigationURL]
+ referrer:[self currentSessionEntryReferrer]
+ transition:[self currentTransition]];
+ [self loadPOSTRequestWithBody:request];
+ };
ProceduralBlock webViewNavigationBlock = ^{
// If the current navigation URL is the same as the URL of the visible
@@ -512,12 +618,24 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
}
};
+ if (POSTData.length && !isFormResubmission) {
+ POSTBlock();
+ return;
+ }
+
+ // If there is no corresponding WKBackForwardListItem, or the item is not in
+ // the current WKWebView's back-forward list, navigating using WKWebView API
+ // is not possible. In this case, fall back to the default navigation
+ // mechanism.
+ if (!holder->back_forward_list_item() ||
+ ![self isBackForwardListItemValid:holder->back_forward_list_item()]) {
+ defaultNavigationBlock();
+ return;
+ }
+
// If the request is not a form submission or resubmission, or the user
// doesn't need to confirm the load, then continue right away.
- web::NavigationItemImpl* currentItem =
- [self currentSessionEntry].navigationItemImpl;
- if ((holder->navigation_type() != WKNavigationTypeFormResubmitted &&
- holder->navigation_type() != WKNavigationTypeFormSubmitted) ||
+ if (!isFormResubmission ||
currentItem->ShouldSkipResubmitDataConfirmation()) {
webViewNavigationBlock();
return;
@@ -525,6 +643,7 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
// If the request is form submission or resubmission, then prompt the
// user before proceeding.
+ DCHECK(isFormResubmission);
[self.delegate webController:self
onFormResubmissionForRequest:nil
continueBlock:webViewNavigationBlock
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698