Chromium Code Reviews| 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 |