OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |