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

Side by Side 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, 2 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 unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h" 5 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
6 6
7 #import <WebKit/WebKit.h> 7 #import <WebKit/WebKit.h>
8 8
9 #include "base/ios/ios_util.h" 9 #include "base/ios/ios_util.h"
10 #include "base/ios/weak_nsobject.h" 10 #include "base/ios/weak_nsobject.h"
11 #include "base/json/json_reader.h" 11 #include "base/json/json_reader.h"
12 #include "base/json/string_escape.h"
12 #import "base/mac/scoped_nsobject.h" 13 #import "base/mac/scoped_nsobject.h"
13 #include "base/macros.h" 14 #include "base/macros.h"
14 #include "base/strings/sys_string_conversions.h" 15 #include "base/strings/sys_string_conversions.h"
15 #include "base/values.h" 16 #include "base/values.h"
16 #import "ios/net/http_response_headers_util.h" 17 #import "ios/net/http_response_headers_util.h"
17 #import "ios/web/crw_network_activity_indicator_manager.h" 18 #import "ios/web/crw_network_activity_indicator_manager.h"
18 #import "ios/web/navigation/crw_session_controller.h" 19 #import "ios/web/navigation/crw_session_controller.h"
19 #import "ios/web/navigation/crw_session_entry.h" 20 #import "ios/web/navigation/crw_session_entry.h"
20 #include "ios/web/navigation/navigation_item_impl.h" 21 #include "ios/web/navigation/navigation_item_impl.h"
21 #include "ios/web/navigation/web_load_params.h" 22 #include "ios/web/navigation/web_load_params.h"
(...skipping 24 matching lines...) Expand all
46 #import "net/base/mac/url_conversions.h" 47 #import "net/base/mac/url_conversions.h"
47 #include "net/ssl/ssl_info.h" 48 #include "net/ssl/ssl_info.h"
48 #include "url/url_constants.h" 49 #include "url/url_constants.h"
49 50
50 namespace { 51 namespace {
51 // Extracts Referer value from WKNavigationAction request header. 52 // Extracts Referer value from WKNavigationAction request header.
52 NSString* GetRefererFromNavigationAction(WKNavigationAction* action) { 53 NSString* GetRefererFromNavigationAction(WKNavigationAction* action) {
53 return [action.request valueForHTTPHeaderField:@"Referer"]; 54 return [action.request valueForHTTPHeaderField:@"Referer"];
54 } 55 }
55 56
57 // Escapes characters and encloses given string in quotes for use in JavaScript.
58 NSString* EscapeAndQuoteStringForJavaScript(NSString* unescapedString) {
59 std::string string = base::SysNSStringToUTF8(unescapedString);
60 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.
61 }
62
56 NSString* const kScriptMessageName = @"crwebinvoke"; 63 NSString* const kScriptMessageName = @"crwebinvoke";
57 NSString* const kScriptImmediateName = @"crwebinvokeimmediate"; 64 NSString* const kScriptImmediateName = @"crwebinvokeimmediate";
58 65
66 // JavaScript template to do a POST request using an XMLHttpRequest.
67 // It takes three arguments (in order):
68 // * The quoted and escaped URL to send a POST request to.
69 // * The HTTP headers of the request. They should be written as valid JavaScript
70 // statements, adding headers to the XMLHttpRequest variable named 'req'
71 // (e.g. 'req.setRequestHeader("Foo", "Bar");').
72 // * The base64 string of POST request body.
73 // * Content-Type string
74 NSString* const kPostRequestTemplate =
75 @"<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.
76 "function b64ToBlob(b64Data, contentType) {"
77 " contentType = contentType || '';"
78 " var sliceSize = 512;"
79 " var byteCharacters = b64Data;"
80 " var byteArrays = [];"
81 " for (var offset = 0; offset < byteCharacters.length; offset += "
82 "sliceSize) {"
83 " var slice = byteCharacters.slice(offset, offset + sliceSize);"
84 " var byteNumbers = new Array(slice.length);"
85 " for (var i = 0; i < slice.length; i++) {"
86 " byteNumbers[i] = slice.charCodeAt(i);"
87 " }"
88 " var byteArray = new Uint8Array(byteNumbers);"
89 " byteArrays.push(byteArray);"
90 " }"
91 " var blob = new Blob(byteArrays, {type: contentType});"
92 " return blob;"
93 "}"
94 " function createAndSendPostRequest() {"
95 " var req = new XMLHttpRequest();"
96 " req.open(\"POST\", %@, false);"
97 " %@" //< This sets request headers.
98 " var blob = b64ToBlob(atob(%@), %@);" //< base64 string and
99 // content type
100 " req.send(blob);"
101 " if (req.status != 200) {"
102 " throw req.status;"
103 " }"
104 " return req.responseText;"
105 " }"
106 " "
107 " document.open();"
108 " document.write(createAndSendPostRequest());"
109 " document.close();"
110
111 "</script></html>";
112
59 // Utility functions for storing the source of NSErrors received by WKWebViews: 113 // Utility functions for storing the source of NSErrors received by WKWebViews:
60 // - Errors received by |-webView:didFailProvisionalNavigation:withError:| are 114 // - Errors received by |-webView:didFailProvisionalNavigation:withError:| are
61 // recorded using WKWebViewErrorSource::PROVISIONAL_LOAD. These should be 115 // recorded using WKWebViewErrorSource::PROVISIONAL_LOAD. These should be
62 // aborted. 116 // aborted.
63 // - Errors received by |-webView:didFailNavigation:withError:| are recorded 117 // - Errors received by |-webView:didFailNavigation:withError:| are recorded
64 // using WKWebViewsource::NAVIGATION. These errors should not be aborted, as 118 // using WKWebViewsource::NAVIGATION. These errors should not be aborted, as
65 // the WKWebView will automatically retry the load. 119 // the WKWebView will automatically retry the load.
66 NSString* const kWKWebViewErrorSourceKey = @"ErrorSource"; 120 NSString* const kWKWebViewErrorSourceKey = @"ErrorSource";
67 typedef enum { NONE = 0, PROVISIONAL_LOAD, NAVIGATION } WKWebViewErrorSource; 121 typedef enum { NONE = 0, PROVISIONAL_LOAD, NAVIGATION } WKWebViewErrorSource;
68 NSError* WKWebViewErrorWithSource(NSError* error, WKWebViewErrorSource source) { 122 NSError* WKWebViewErrorWithSource(NSError* error, WKWebViewErrorSource source) {
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
162 @property(nonatomic, readonly) NSString* requestGroupIDForUserAgent; 216 @property(nonatomic, readonly) NSString* requestGroupIDForUserAgent;
163 217
164 // Activity indicator group ID for this web controller. 218 // Activity indicator group ID for this web controller.
165 @property(nonatomic, readonly) NSString* activityIndicatorGroupID; 219 @property(nonatomic, readonly) NSString* activityIndicatorGroupID;
166 220
167 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) 221 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
168 // Identifier used for storing and retrieving certificates. 222 // Identifier used for storing and retrieving certificates.
169 @property(nonatomic, readonly) int certGroupID; 223 @property(nonatomic, readonly) int certGroupID;
170 #endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) 224 #endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
171 225
226 // Constructs an HTML page that executes the request through JavaScript and
227 // replaces document with the result.
228 - (NSString*)HTMLForPOSTRequest:(NSURLRequest*)request;
229
230 // Loads POST request with body in |_wkWebView| by constructing an HTML page
231 // that executes the request through javascript and replaces document with the
232 // result.
233 // Note that this approach includes multiple body encodings and decodings, plus
234 // the data is passed to |_wkWebView| on main thread, hence the performance is
235 // expected to be low.
236 // This is necessary because WKWebView ignores POST request body.
237 // Workaround for https://bugs.webkit.org/show_bug.cgi?id=145410
238 - (void)loadPOSTRequestWithBody:(NSMutableURLRequest*)request;
239
172 // Returns the WKWebViewConfigurationProvider associated with the web 240 // Returns the WKWebViewConfigurationProvider associated with the web
173 // controller's BrowserState. 241 // controller's BrowserState.
174 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider; 242 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider;
175 243
176 // Creates a web view with given |config|. No-op if web view is already created. 244 // Creates a web view with given |config|. No-op if web view is already created.
177 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config; 245 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config;
178 246
179 // Returns a new autoreleased web view created with given configuration. 247 // Returns a new autoreleased web view created with given configuration.
180 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config; 248 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config;
181 249
(...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after
441 return web::WEB_VIEW_DOCUMENT_TYPE_HTML; 509 return web::WEB_VIEW_DOCUMENT_TYPE_HTML;
442 } 510 }
443 511
444 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC; 512 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
445 } 513 }
446 514
447 - (void)loadRequest:(NSMutableURLRequest*)request { 515 - (void)loadRequest:(NSMutableURLRequest*)request {
448 [_wkWebView loadRequest:request]; 516 [_wkWebView loadRequest:request];
449 } 517 }
450 518
519 - (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.
520 NSString* base64Data = [[request HTTPBody] base64EncodedStringWithOptions:0];
521 NSString* originURL = [[request URL] absoluteString];
522 NSString* contentType = nil;
523 NSMutableString* headerString = [NSMutableString string];
524 for (NSString* headerField in [[request allHTTPHeaderFields] allKeys]) {
525 NSString* value = [request allHTTPHeaderFields][headerField];
526 if ([headerField isEqualToString:@"Content-Type"]) {
527 contentType = value;
528 }
529 [headerString appendFormat:@"req.setRequestHeader(%@, %@);",
530 EscapeAndQuoteStringForJavaScript(headerField),
531 EscapeAndQuoteStringForJavaScript(value)];
532 }
533
534 return [NSString stringWithFormat:kPostRequestTemplate,
535 EscapeAndQuoteStringForJavaScript(originURL),
536 headerString,
537 EscapeAndQuoteStringForJavaScript(base64Data),
538 EscapeAndQuoteStringForJavaScript(contentType)];
539 }
540
541 - (void)loadPOSTRequestWithBody:(NSMutableURLRequest*)request {
542 NSString* HTML = [self HTMLForPOSTRequest:request];
543 [_wkWebView loadHTMLString:HTML baseURL:request.URL];
544 }
545
451 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL { 546 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
452 [_wkWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)]; 547 [_wkWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
453 } 548 }
454 549
455 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass 550 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
456 presenceBeacon:(NSString*)beacon { 551 presenceBeacon:(NSString*)beacon {
457 return [_injectedScriptManagers containsObject:jsInjectionManagerClass]; 552 return [_injectedScriptManagers containsObject:jsInjectionManagerClass];
458 } 553 }
459 554
460 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass { 555 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
461 // Skip evaluation if there's no content (e.g., if what's being injected is 556 // Skip evaluation if there's no content (e.g., if what's being injected is
462 // an umbrella manager). 557 // an umbrella manager).
463 if ([script length]) { 558 if ([script length]) {
464 [super injectScript:script forClass:JSInjectionManagerClass]; 559 [super injectScript:script forClass:JSInjectionManagerClass];
465 // Every injection except windowID requires windowID check. 560 // Every injection except windowID requires windowID check.
466 if (JSInjectionManagerClass != [CRWJSWindowIdManager class]) 561 if (JSInjectionManagerClass != [CRWJSWindowIdManager class])
467 script = [self scriptByAddingWindowIDCheckForScript:script]; 562 script = [self scriptByAddingWindowIDCheckForScript:script];
468 web::EvaluateJavaScript(_wkWebView, script, nil); 563 web::EvaluateJavaScript(_wkWebView, script, nil);
469 } 564 }
470 [_injectedScriptManagers addObject:JSInjectionManagerClass]; 565 [_injectedScriptManagers addObject:JSInjectionManagerClass];
471 } 566 }
472 567
473 - (void)willLoadCurrentURLInWebView { 568 - (void)willLoadCurrentURLInWebView {
474 // TODO(stuartmorgan): Get a WKWebView version of the request ID verification 569 // TODO(stuartmorgan): Get a WKWebView version of the request ID verification
475 // code working for debug builds. 570 // code working for debug builds.
476 } 571 }
477 572
478 - (void)loadRequestForCurrentNavigationItem { 573 - (void)loadRequestForCurrentNavigationItem {
479 DCHECK(self.webView && !self.nativeController); 574 DCHECK(self.webView && !self.nativeController);
575 DCHECK([self currentSessionEntry]);
576
577 web::WKBackForwardListItemHolder* holder =
578 [self currentBackForwardListItemHolder];
579 BOOL isFormResubmission =
580 (holder->navigation_type() == WKNavigationTypeFormResubmitted ||
581 holder->navigation_type() == WKNavigationTypeFormSubmitted);
582 web::NavigationItemImpl* currentItem =
583 [self currentSessionEntry].navigationItemImpl;
584 NSData* POSTData = currentItem->GetPostData();
585 NSMutableURLRequest* request = [self requestForCurrentNavigationItem];
480 586
481 ProceduralBlock defaultNavigationBlock = ^{ 587 ProceduralBlock defaultNavigationBlock = ^{
482 [self registerLoadRequest:[self currentNavigationURL] 588 [self registerLoadRequest:[self currentNavigationURL]
483 referrer:[self currentSessionEntryReferrer] 589 referrer:[self currentSessionEntryReferrer]
484 transition:[self currentTransition]]; 590 transition:[self currentTransition]];
485 [self loadRequest:[self requestForCurrentNavigationItem]]; 591 [self loadRequest:request];
486 }; 592 };
487 593
488 // If there is no corresponding WKBackForwardListItem, or the item is not in 594 // 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.
489 // the current WKWebView's back-forward list, navigating using WKWebView API 595 // run the POST request.
490 // is not possible. In this case, fall back to the default navigation 596 ProceduralBlock POSTBlock = ^{
491 // mechanism. 597 [request setHTTPMethod:@"POST"];
492 web::WKBackForwardListItemHolder* holder = 598 [request setHTTPBody:POSTData];
493 [self currentBackForwardListItemHolder]; 599 [request setAllHTTPHeaderFields:[self currentHTTPHeaders]];
494 if (!holder->back_forward_list_item() || 600 [self registerLoadRequest:[self currentNavigationURL]
495 ![self isBackForwardListItemValid:holder->back_forward_list_item()]) { 601 referrer:[self currentSessionEntryReferrer]
496 defaultNavigationBlock(); 602 transition:[self currentTransition]];
497 return; 603 [self loadPOSTRequestWithBody:request];
498 } 604 };
499 605
500 ProceduralBlock webViewNavigationBlock = ^{ 606 ProceduralBlock webViewNavigationBlock = ^{
501 // If the current navigation URL is the same as the URL of the visible 607 // If the current navigation URL is the same as the URL of the visible
502 // page, that means the user requested a reload. |goToBackForwardListItem| 608 // page, that means the user requested a reload. |goToBackForwardListItem|
503 // will be a no-op when it is passed the current back forward list item, 609 // will be a no-op when it is passed the current back forward list item,
504 // so |reload| must be explicitly called. 610 // so |reload| must be explicitly called.
505 [self registerLoadRequest:[self currentNavigationURL] 611 [self registerLoadRequest:[self currentNavigationURL]
506 referrer:[self currentSessionEntryReferrer] 612 referrer:[self currentSessionEntryReferrer]
507 transition:[self currentTransition]]; 613 transition:[self currentTransition]];
508 if ([self currentNavigationURL] == net::GURLWithNSURL([_wkWebView URL])) { 614 if ([self currentNavigationURL] == net::GURLWithNSURL([_wkWebView URL])) {
509 [_wkWebView reload]; 615 [_wkWebView reload];
510 } else { 616 } else {
511 [_wkWebView goToBackForwardListItem:holder->back_forward_list_item()]; 617 [_wkWebView goToBackForwardListItem:holder->back_forward_list_item()];
512 } 618 }
513 }; 619 };
514 620
621 if (POSTData.length && !isFormResubmission) {
622 POSTBlock();
623 return;
624 }
625
626 // If there is no corresponding WKBackForwardListItem, or the item is not in
627 // the current WKWebView's back-forward list, navigating using WKWebView API
628 // is not possible. In this case, fall back to the default navigation
629 // mechanism.
630 if (!holder->back_forward_list_item() ||
631 ![self isBackForwardListItemValid:holder->back_forward_list_item()]) {
632 defaultNavigationBlock();
633 return;
634 }
635
515 // If the request is not a form submission or resubmission, or the user 636 // If the request is not a form submission or resubmission, or the user
516 // doesn't need to confirm the load, then continue right away. 637 // doesn't need to confirm the load, then continue right away.
517 web::NavigationItemImpl* currentItem = 638 if (!isFormResubmission ||
518 [self currentSessionEntry].navigationItemImpl;
519 if ((holder->navigation_type() != WKNavigationTypeFormResubmitted &&
520 holder->navigation_type() != WKNavigationTypeFormSubmitted) ||
521 currentItem->ShouldSkipResubmitDataConfirmation()) { 639 currentItem->ShouldSkipResubmitDataConfirmation()) {
522 webViewNavigationBlock(); 640 webViewNavigationBlock();
523 return; 641 return;
524 } 642 }
525 643
526 // If the request is form submission or resubmission, then prompt the 644 // If the request is form submission or resubmission, then prompt the
527 // user before proceeding. 645 // user before proceeding.
646 DCHECK(isFormResubmission);
528 [self.delegate webController:self 647 [self.delegate webController:self
529 onFormResubmissionForRequest:nil 648 onFormResubmissionForRequest:nil
530 continueBlock:webViewNavigationBlock 649 continueBlock:webViewNavigationBlock
531 cancelBlock:defaultNavigationBlock]; 650 cancelBlock:defaultNavigationBlock];
532 } 651 }
533 652
534 // Overrides the hashchange workaround in the super class that manually 653 // Overrides the hashchange workaround in the super class that manually
535 // triggers Javascript hashchange events. If navigating with native API, 654 // triggers Javascript hashchange events. If navigating with native API,
536 // i.e. using a back forward list item, hashchange events will be triggered 655 // i.e. using a back forward list item, hashchange events will be triggered
537 // automatically, so no URL tampering is required. 656 // automatically, so no URL tampering is required.
(...skipping 948 matching lines...) Expand 10 before | Expand all | Expand 10 after
1486 placeholderText:defaultText 1605 placeholderText:defaultText
1487 requestURL: 1606 requestURL:
1488 net::GURLWithNSURL(frame.request.URL) 1607 net::GURLWithNSURL(frame.request.URL)
1489 completionHandler:completionHandler]; 1608 completionHandler:completionHandler];
1490 } else if (completionHandler) { 1609 } else if (completionHandler) {
1491 completionHandler(nil); 1610 completionHandler(nil);
1492 } 1611 }
1493 } 1612 }
1494 1613
1495 @end 1614 @end
OLDNEW
« 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