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

Side by Side Diff: ios/web/web_state/ui/crw_web_controller.mm

Issue 2821173002: Handle correctly start and commit of non-latest navigations. (Closed)
Patch Set: Updated comments Created 3 years, 8 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 | « ios/web/navigation/navigation_manager_impl_unittest.mm ('k') | 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 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 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_web_controller.h" 5 #import "ios/web/web_state/ui/crw_web_controller.h"
6 6
7 #import <WebKit/WebKit.h> 7 #import <WebKit/WebKit.h>
8 8
9 #import <objc/runtime.h> 9 #import <objc/runtime.h>
10 #include <stddef.h> 10 #include <stddef.h>
(...skipping 598 matching lines...) Expand 10 before | Expand all | Expand 10 after
609 // Updates the internal state and informs the delegate that any outstanding load 609 // Updates the internal state and informs the delegate that any outstanding load
610 // operations are cancelled. 610 // operations are cancelled.
611 - (void)loadCancelled; 611 - (void)loadCancelled;
612 // If YES, the page should be closed if it successfully redirects to a native 612 // If YES, the page should be closed if it successfully redirects to a native
613 // application, for example if a new tab redirects to the App Store. 613 // application, for example if a new tab redirects to the App Store.
614 - (BOOL)shouldClosePageOnNativeApplicationLoad; 614 - (BOOL)shouldClosePageOnNativeApplicationLoad;
615 // Called following navigation completion to generate final navigation lifecycle 615 // Called following navigation completion to generate final navigation lifecycle
616 // events. Navigation is considered complete when the document has finished 616 // events. Navigation is considered complete when the document has finished
617 // loading, or when other page load mechanics are completed on a 617 // loading, or when other page load mechanics are completed on a
618 // non-document-changing URL change. 618 // non-document-changing URL change.
619 - (void)didFinishNavigation; 619 - (void)didFinishNavigation:(WKNavigation*)navigation;
620 // Update the appropriate parts of the model and broadcast to the embedder. This 620 // Update the appropriate parts of the model and broadcast to the embedder. This
621 // may be called multiple times and thus must be idempotent. 621 // may be called multiple times and thus must be idempotent.
622 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess; 622 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess
623 forNavigation:(WKNavigation*)navigation;
623 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED. 624 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
624 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess; 625 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
625 // Navigates forwards or backwards by |delta| pages. No-op if delta is out of 626 // Navigates forwards or backwards by |delta| pages. No-op if delta is out of
626 // bounds. Reloads if delta is 0. 627 // bounds. Reloads if delta is 0.
627 // TODO(crbug.com/661316): Move this method to NavigationManager. 628 // TODO(crbug.com/661316): Move this method to NavigationManager.
628 - (void)goDelta:(int)delta; 629 - (void)goDelta:(int)delta;
629 // Informs the native controller if web usage is allowed or not. 630 // Informs the native controller if web usage is allowed or not.
630 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled; 631 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
631 // Called when web controller receives a new message from the web page. 632 // Called when web controller receives a new message from the web page.
632 - (void)didReceiveScriptMessage:(WKScriptMessage*)message; 633 - (void)didReceiveScriptMessage:(WKScriptMessage*)message;
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
749 sourceURL:(const GURL&)sourceURL 750 sourceURL:(const GURL&)sourceURL
750 linkActivatedNavigation:(BOOL)linkActivatedNavigation; 751 linkActivatedNavigation:(BOOL)linkActivatedNavigation;
751 // Returns YES if the navigation action is associated with a main frame request. 752 // Returns YES if the navigation action is associated with a main frame request.
752 - (BOOL)isMainFrameNavigationAction:(WKNavigationAction*)action; 753 - (BOOL)isMainFrameNavigationAction:(WKNavigationAction*)action;
753 // Returns whether external URL navigation action should be opened. 754 // Returns whether external URL navigation action should be opened.
754 - (BOOL)shouldOpenExternalURLForNavigationAction:(WKNavigationAction*)action; 755 - (BOOL)shouldOpenExternalURLForNavigationAction:(WKNavigationAction*)action;
755 // Updates SSL status for the current navigation item based on the information 756 // Updates SSL status for the current navigation item based on the information
756 // provided by web view. 757 // provided by web view.
757 - (void)updateSSLStatusForCurrentNavigationItem; 758 - (void)updateSSLStatusForCurrentNavigationItem;
758 // Called when a load ends in an SSL error and certificate chain. 759 // Called when a load ends in an SSL error and certificate chain.
759 - (void)handleSSLCertError:(NSError*)error; 760 - (void)handleSSLCertError:(NSError*)error forNavigation:navigation;
760 761
761 // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to 762 // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to
762 // reply with NSURLSessionAuthChallengeDisposition and credentials. 763 // reply with NSURLSessionAuthChallengeDisposition and credentials.
763 - (void)processAuthChallenge:(NSURLAuthenticationChallenge*)challenge 764 - (void)processAuthChallenge:(NSURLAuthenticationChallenge*)challenge
764 forCertAcceptPolicy:(web::CertAcceptPolicy)policy 765 forCertAcceptPolicy:(web::CertAcceptPolicy)policy
765 certStatus:(net::CertStatus)certStatus 766 certStatus:(net::CertStatus)certStatus
766 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, 767 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,
767 NSURLCredential*))completionHandler; 768 NSURLCredential*))completionHandler;
768 // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to reply 769 // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to reply
769 // with NSURLSessionAuthChallengeDisposition and credentials. 770 // with NSURLSessionAuthChallengeDisposition and credentials.
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
882 (base::DictionaryValue*)message 883 (base::DictionaryValue*)message
883 context:(NSDictionary*)context; 884 context:(NSDictionary*)context;
884 885
885 // Returns YES if the given |action| should be allowed to continue. 886 // Returns YES if the given |action| should be allowed to continue.
886 // If this returns NO, the load should be cancelled. 887 // If this returns NO, the load should be cancelled.
887 - (BOOL)shouldAllowLoadWithNavigationAction:(WKNavigationAction*)action; 888 - (BOOL)shouldAllowLoadWithNavigationAction:(WKNavigationAction*)action;
888 // Called when a load ends in an error. 889 // Called when a load ends in an error.
889 // TODO(stuartmorgan): Figure out if there's actually enough shared logic that 890 // TODO(stuartmorgan): Figure out if there's actually enough shared logic that
890 // this makes sense. At the very least remove inMainFrame since that only makes 891 // this makes sense. At the very least remove inMainFrame since that only makes
891 // sense for UIWebView. 892 // sense for UIWebView.
892 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame; 893 - (void)handleLoadError:(NSError*)error
894 inMainFrame:(BOOL)inMainFrame
895 forNavigation:(WKNavigation*)navigation;
893 896
894 // Handles cancelled load in WKWebView (error with NSURLErrorCancelled code). 897 // Handles cancelled load in WKWebView (error with NSURLErrorCancelled code).
895 - (void)handleCancelledError:(NSError*)error; 898 - (void)handleCancelledError:(NSError*)error;
896 899
897 // Used to decide whether a load that generates errors with the 900 // Used to decide whether a load that generates errors with the
898 // NSURLErrorCancelled code should be cancelled. 901 // NSURLErrorCancelled code should be cancelled.
899 - (BOOL)shouldCancelLoadForCancelledError:(NSError*)error; 902 - (BOOL)shouldCancelLoadForCancelledError:(NSError*)error;
900 903
901 // Sets up WebUI for URL. 904 // Sets up WebUI for URL.
902 - (void)createWebUIForURL:(const GURL&)URL; 905 - (void)createWebUIForURL:(const GURL&)URL;
(...skipping 1160 matching lines...) Expand 10 before | Expand all | Expand 10 after
2063 2066
2064 // Update the user agent before attempting the navigation. 2067 // Update the user agent before attempting the navigation.
2065 web::NavigationItem* toItem = items[index].get(); 2068 web::NavigationItem* toItem = items[index].get();
2066 [self updateWebViewUserAgentFromUserAgentType:toItem->GetUserAgentType()]; 2069 [self updateWebViewUserAgentFromUserAgentType:toItem->GetUserAgentType()];
2067 2070
2068 web::NavigationItem* fromItem = sessionController.currentItem; 2071 web::NavigationItem* fromItem = sessionController.currentItem;
2069 BOOL sameDocumentNavigation = 2072 BOOL sameDocumentNavigation =
2070 [sessionController isSameDocumentNavigationBetweenItem:fromItem 2073 [sessionController isSameDocumentNavigationBetweenItem:fromItem
2071 andItem:toItem]; 2074 andItem:toItem];
2072 if (sameDocumentNavigation) { 2075 if (sameDocumentNavigation) {
2073 [sessionController goToItemAtIndex:index]; 2076 [sessionController goToItemAtIndex:index discardNonCommittedItems:YES];
2074 [self updateHTML5HistoryState]; 2077 [self updateHTML5HistoryState];
2075 } else { 2078 } else {
2076 [sessionController discardNonCommittedItems]; 2079 [sessionController discardNonCommittedItems];
2077 [sessionController setPendingItemIndex:index]; 2080 [sessionController setPendingItemIndex:index];
2078 2081
2079 web::NavigationItemImpl* pendingItem = sessionController.pendingItem; 2082 web::NavigationItemImpl* pendingItem = sessionController.pendingItem;
2080 pendingItem->SetTransitionType(ui::PageTransitionFromInt( 2083 pendingItem->SetTransitionType(ui::PageTransitionFromInt(
2081 pendingItem->GetTransitionType() | ui::PAGE_TRANSITION_FORWARD_BACK)); 2084 pendingItem->GetTransitionType() | ui::PAGE_TRANSITION_FORWARD_BACK));
2082 2085
2083 [self loadCurrentURL]; 2086 [self loadCurrentURL];
2084 } 2087 }
2085 } 2088 }
2086 2089
2087 - (BOOL)isLoaded { 2090 - (BOOL)isLoaded {
2088 return _loadPhase == web::PAGE_LOADED; 2091 return _loadPhase == web::PAGE_LOADED;
2089 } 2092 }
2090 2093
2091 - (void)didFinishNavigation { 2094 - (void)didFinishNavigation:(WKNavigation*)navigation {
2092 // This can be called at multiple times after the document has loaded. Do 2095 // This can be called at multiple times after the document has loaded. Do
2093 // nothing if the document has already loaded. 2096 // nothing if the document has already loaded.
2094 if (_loadPhase == web::PAGE_LOADED) 2097 if (_loadPhase == web::PAGE_LOADED)
2095 return; 2098 return;
2096 [self loadCompleteWithSuccess:YES]; 2099 [self loadCompleteWithSuccess:YES forNavigation:navigation];
2097 } 2100 }
2098 2101
2099 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess { 2102 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess
2103 forNavigation:(WKNavigation*)navigation {
2100 [self removePlaceholderOverlay]; 2104 [self removePlaceholderOverlay];
2101 // The webView may have been torn down (or replaced by a native view). Be 2105 // The webView may have been torn down (or replaced by a native view). Be
2102 // safe and do nothing if that's happened. 2106 // safe and do nothing if that's happened.
2103 if (_loadPhase != web::PAGE_LOADING) 2107 if (_loadPhase != web::PAGE_LOADING)
2104 return; 2108 return;
2105 2109
2106 DCHECK(_webView); 2110 DCHECK(_webView);
2107 2111
2108 const GURL currentURL([self currentURL]); 2112 const GURL currentURL([self currentURL]);
2109 2113
2110 _loadPhase = web::PAGE_LOADED; 2114 _loadPhase = web::PAGE_LOADED;
2111 2115
2112 [self optOutScrollsToTopForSubviews]; 2116 [self optOutScrollsToTopForSubviews];
2113 2117
2114 // Ensure the URL is as expected (and already reported to the delegate). 2118 DCHECK((currentURL == _lastRegisteredRequestURL) || // latest navigation
2115 // If |_lastRegisteredRequestURL| is invalid then |currentURL| will be 2119 // previous navigation
2116 // "about:blank". 2120 ![[_navigationStates lastAddedNavigation] isEqual:navigation] ||
2117 DCHECK((currentURL == _lastRegisteredRequestURL) || 2121 // invalid URL load
2118 (!_lastRegisteredRequestURL.is_valid() && 2122 (!_lastRegisteredRequestURL.is_valid() &&
2119 _documentURL.spec() == url::kAboutBlankURL)) 2123 _documentURL.spec() == url::kAboutBlankURL))
2120 << std::endl 2124 << std::endl
2121 << "currentURL = [" << currentURL << "]" << std::endl 2125 << "currentURL = [" << currentURL << "]" << std::endl
2122 << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]"; 2126 << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
2123 2127
2124 // Perform post-load-finished updates. 2128 // Perform post-load-finished updates.
2125 [self didFinishWithURL:currentURL loadSuccess:loadSuccess]; 2129 [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
2126 2130
2127 // Execute the pending LoadCompleteActions. 2131 // Execute the pending LoadCompleteActions.
(...skipping 688 matching lines...) Expand 10 before | Expand all | Expand 10 after
2816 transition:transition]; 2820 transition:transition];
2817 2821
2818 NSString* replaceWebViewJS = 2822 NSString* replaceWebViewJS =
2819 [self javaScriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject]; 2823 [self javaScriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2820 base::WeakNSObject<CRWWebController> weakSelf(self); 2824 base::WeakNSObject<CRWWebController> weakSelf(self);
2821 [self executeJavaScript:replaceWebViewJS completionHandler:^(id, NSError*) { 2825 [self executeJavaScript:replaceWebViewJS completionHandler:^(id, NSError*) {
2822 if (!weakSelf || weakSelf.get()->_isBeingDestroyed) 2826 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2823 return; 2827 return;
2824 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]); 2828 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2825 [strongSelf optOutScrollsToTopForSubviews]; 2829 [strongSelf optOutScrollsToTopForSubviews];
2826 [strongSelf didFinishNavigation]; 2830 [strongSelf didFinishNavigation:nil];
2827 }]; 2831 }];
2828 return YES; 2832 return YES;
2829 } 2833 }
2830 2834
2831 - (BOOL)handleWindowHistoryDidReplaceStateMessage: 2835 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2832 (base::DictionaryValue*)message 2836 (base::DictionaryValue*)message
2833 context:(NSDictionary*)context { 2837 context:(NSDictionary*)context {
2834 DCHECK(_changingHistoryState); 2838 DCHECK(_changingHistoryState);
2835 _changingHistoryState = NO; 2839 _changingHistoryState = NO;
2836 2840
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2871 _URLOnStartLoading = replaceURL; 2875 _URLOnStartLoading = replaceURL;
2872 _lastRegisteredRequestURL = replaceURL; 2876 _lastRegisteredRequestURL = replaceURL;
2873 [self replaceStateWithPageURL:replaceURL stateObject:stateObject]; 2877 [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2874 NSString* replaceStateJS = [self javaScriptToReplaceWebViewURL:replaceURL 2878 NSString* replaceStateJS = [self javaScriptToReplaceWebViewURL:replaceURL
2875 stateObjectJSON:stateObject]; 2879 stateObjectJSON:stateObject];
2876 base::WeakNSObject<CRWWebController> weakSelf(self); 2880 base::WeakNSObject<CRWWebController> weakSelf(self);
2877 [self executeJavaScript:replaceStateJS completionHandler:^(id, NSError*) { 2881 [self executeJavaScript:replaceStateJS completionHandler:^(id, NSError*) {
2878 if (!weakSelf || weakSelf.get()->_isBeingDestroyed) 2882 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2879 return; 2883 return;
2880 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]); 2884 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2881 [strongSelf didFinishNavigation]; 2885 [strongSelf didFinishNavigation:nil];
2882 }]; 2886 }];
2883 return YES; 2887 return YES;
2884 } 2888 }
2885 2889
2886 #pragma mark - 2890 #pragma mark -
2887 2891
2888 - (BOOL)wantsKeyboardShield { 2892 - (BOOL)wantsKeyboardShield {
2889 if ([self.nativeController 2893 if ([self.nativeController
2890 respondsToSelector:@selector(wantsKeyboardShield)]) { 2894 respondsToSelector:@selector(wantsKeyboardShield)]) {
2891 return [self.nativeController wantsKeyboardShield]; 2895 return [self.nativeController wantsKeyboardShield];
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after
3106 web::NavigationItemImpl* item = self.currentNavItem; 3110 web::NavigationItemImpl* item = self.currentNavItem;
3107 // TODO(crbug.com/570699): Remove this check once it's no longer possible to 3111 // TODO(crbug.com/570699): Remove this check once it's no longer possible to
3108 // have no current entries. 3112 // have no current entries.
3109 if (item) 3113 if (item)
3110 [self cachePOSTDataForRequest:request inNavigationItem:item]; 3114 [self cachePOSTDataForRequest:request inNavigationItem:item];
3111 } 3115 }
3112 3116
3113 return YES; 3117 return YES;
3114 } 3118 }
3115 3119
3116 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame { 3120 - (void)handleLoadError:(NSError*)error
3121 inMainFrame:(BOOL)inMainFrame
3122 forNavigation:(WKNavigation*)navigation {
3117 NSString* MIMEType = [_pendingNavigationInfo MIMEType]; 3123 NSString* MIMEType = [_pendingNavigationInfo MIMEType];
3118 if ([_passKitDownloader isMIMETypePassKitType:MIMEType]) 3124 if ([_passKitDownloader isMIMETypePassKitType:MIMEType])
3119 return; 3125 return;
3120 if ([error code] == NSURLErrorUnsupportedURL) 3126 if ([error code] == NSURLErrorUnsupportedURL)
3121 return; 3127 return;
3122 // In cases where a Plug-in handles the load do not take any further action. 3128 // In cases where a Plug-in handles the load do not take any further action.
3123 if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] && 3129 if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] &&
3124 (error.code == web::kWebKitErrorPlugInLoadFailed || 3130 (error.code == web::kWebKitErrorPlugInLoadFailed ||
3125 error.code == web::kWebKitErrorCannotShowUrl)) 3131 error.code == web::kWebKitErrorCannotShowUrl))
3126 return; 3132 return;
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
3169 if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] && 3175 if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] &&
3170 error.code == web::kWebKitErrorFrameLoadInterruptedByPolicyChange) { 3176 error.code == web::kWebKitErrorFrameLoadInterruptedByPolicyChange) {
3171 // See if the delegate wants to handle this case. 3177 // See if the delegate wants to handle this case.
3172 if (errorGURL.is_valid() && 3178 if (errorGURL.is_valid() &&
3173 [_delegate 3179 [_delegate
3174 respondsToSelector:@selector( 3180 respondsToSelector:@selector(
3175 controllerForUnhandledContentAtURL:)]) { 3181 controllerForUnhandledContentAtURL:)]) {
3176 id<CRWNativeContent> controller = 3182 id<CRWNativeContent> controller =
3177 [_delegate controllerForUnhandledContentAtURL:errorGURL]; 3183 [_delegate controllerForUnhandledContentAtURL:errorGURL];
3178 if (controller) { 3184 if (controller) {
3179 [self loadCompleteWithSuccess:NO]; 3185 [self loadCompleteWithSuccess:NO forNavigation:navigation];
3180 [self removeWebViewAllowingCachedReconstruction:NO]; 3186 [self removeWebViewAllowingCachedReconstruction:NO];
3181 [self setNativeController:controller]; 3187 [self setNativeController:controller];
3182 [self loadNativeViewWithSuccess:YES]; 3188 [self loadNativeViewWithSuccess:YES];
3183 return; 3189 return;
3184 } 3190 }
3185 } 3191 }
3186 3192
3187 // Ignore errors that originate from URLs that are opened in external apps. 3193 // Ignore errors that originate from URLs that are opened in external apps.
3188 if ([_openedApplicationURL containsObject:errorURL]) 3194 if ([_openedApplicationURL containsObject:errorURL])
3189 return; 3195 return;
3190 // Certain frame errors don't have URL information for some reason; for 3196 // Certain frame errors don't have URL information for some reason; for
3191 // those cases (so far the only known case is plugin content loaded directly 3197 // those cases (so far the only known case is plugin content loaded directly
3192 // in a frame) just ignore the error. See crbug.com/414295 3198 // in a frame) just ignore the error. See crbug.com/414295
3193 if (!errorURL) { 3199 if (!errorURL) {
3194 DCHECK(!inMainFrame); 3200 DCHECK(!inMainFrame);
3195 return; 3201 return;
3196 } 3202 }
3197 // The wrapper error uses the URL of the error and not the requested URL 3203 // The wrapper error uses the URL of the error and not the requested URL
3198 // (which can be different in case of a redirect) to match desktop Chrome 3204 // (which can be different in case of a redirect) to match desktop Chrome
3199 // behavior. 3205 // behavior.
3200 NSError* wrapperError = [NSError 3206 NSError* wrapperError = [NSError
3201 errorWithDomain:[error domain] 3207 errorWithDomain:[error domain]
3202 code:[error code] 3208 code:[error code]
3203 userInfo:@{ 3209 userInfo:@{
3204 NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString], 3210 NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
3205 NSUnderlyingErrorKey : error 3211 NSUnderlyingErrorKey : error
3206 }]; 3212 }];
3207 [self loadCompleteWithSuccess:NO]; 3213 [self loadCompleteWithSuccess:NO forNavigation:navigation];
3208 [self loadErrorInNativeView:wrapperError]; 3214 [self loadErrorInNativeView:wrapperError];
3209 return; 3215 return;
3210 } 3216 }
3211 3217
3212 if ([error code] == NSURLErrorCancelled) { 3218 if ([error code] == NSURLErrorCancelled) {
3213 [self handleCancelledError:error]; 3219 [self handleCancelledError:error];
3214 // NSURLErrorCancelled errors that aren't handled by aborting the load will 3220 // NSURLErrorCancelled errors that aren't handled by aborting the load will
3215 // automatically be retried by the web view, so early return in this case. 3221 // automatically be retried by the web view, so early return in this case.
3216 return; 3222 return;
3217 } 3223 }
3218 3224
3219 [self loadCompleteWithSuccess:NO]; 3225 [self loadCompleteWithSuccess:NO forNavigation:navigation];
3220 [self loadErrorInNativeView:error]; 3226 [self loadErrorInNativeView:error];
3221 } 3227 }
3222 3228
3223 - (void)handleCancelledError:(NSError*)error { 3229 - (void)handleCancelledError:(NSError*)error {
3224 if ([self shouldCancelLoadForCancelledError:error]) { 3230 if ([self shouldCancelLoadForCancelledError:error]) {
3225 [self loadCancelled]; 3231 [self loadCancelled];
3226 [[self sessionController] discardNonCommittedItems]; 3232 [[self sessionController] discardNonCommittedItems];
3227 } 3233 }
3228 } 3234 }
3229 3235
(...skipping 712 matching lines...) Expand 10 before | Expand all | Expand 10 after
3942 3948
3943 - (void)didShowCreditCardInputOnHTTP { 3949 - (void)didShowCreditCardInputOnHTTP {
3944 DCHECK(!web::IsOriginSecure(self.webState->GetLastCommittedURL())); 3950 DCHECK(!web::IsOriginSecure(self.webState->GetLastCommittedURL()));
3945 web::NavigationItem* item = 3951 web::NavigationItem* item =
3946 _webStateImpl->GetNavigationManager()->GetLastCommittedItem(); 3952 _webStateImpl->GetNavigationManager()->GetLastCommittedItem();
3947 item->GetSSL().content_status |= 3953 item->GetSSL().content_status |=
3948 web::SSLStatus::DISPLAYED_CREDIT_CARD_FIELD_ON_HTTP; 3954 web::SSLStatus::DISPLAYED_CREDIT_CARD_FIELD_ON_HTTP;
3949 _webStateImpl->OnVisibleSecurityStateChange(); 3955 _webStateImpl->OnVisibleSecurityStateChange();
3950 } 3956 }
3951 3957
3952 - (void)handleSSLCertError:(NSError*)error { 3958 - (void)handleSSLCertError:(NSError*)error
3959 forNavigation:(WKNavigation*)navigation {
3953 CHECK(web::IsWKWebViewSSLCertError(error)); 3960 CHECK(web::IsWKWebViewSSLCertError(error));
3954 3961
3955 net::SSLInfo info; 3962 net::SSLInfo info;
3956 web::GetSSLInfoFromWKWebViewSSLCertError(error, &info); 3963 web::GetSSLInfoFromWKWebViewSSLCertError(error, &info);
3957 3964
3958 if (!info.cert) { 3965 if (!info.cert) {
3959 // |info.cert| can be null if certChain in NSError is empty or can not be 3966 // |info.cert| can be null if certChain in NSError is empty or can not be
3960 // parsed, in this case do not ask delegate if error should be allowed, it 3967 // parsed, in this case do not ask delegate if error should be allowed, it
3961 // should not be. 3968 // should not be.
3962 [self handleLoadError:error inMainFrame:YES]; 3969 [self handleLoadError:error inMainFrame:YES forNavigation:navigation];
3963 return; 3970 return;
3964 } 3971 }
3965 3972
3966 // Retrieve verification results from _certVerificationErrors cache to avoid 3973 // Retrieve verification results from _certVerificationErrors cache to avoid
3967 // unnecessary recalculations. Verification results are cached for the leaf 3974 // unnecessary recalculations. Verification results are cached for the leaf
3968 // cert, because the cert chain in |didReceiveAuthenticationChallenge:| is 3975 // cert, because the cert chain in |didReceiveAuthenticationChallenge:| is
3969 // the OS constructed chain, while |chain| is the chain from the server. 3976 // the OS constructed chain, while |chain| is the chain from the server.
3970 NSArray* chain = error.userInfo[web::kNSErrorPeerCertificateChainKey]; 3977 NSArray* chain = error.userInfo[web::kNSErrorPeerCertificateChainKey];
3971 NSURL* requestURL = error.userInfo[web::kNSErrorFailingURLKey]; 3978 NSURL* requestURL = error.userInfo[web::kNSErrorFailingURLKey];
3972 NSString* host = [requestURL host]; 3979 NSString* host = [requestURL host];
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after
4152 } 4159 }
4153 4160
4154 CRWWKScriptMessageRouter* messageRouter = 4161 CRWWKScriptMessageRouter* messageRouter =
4155 [self webViewConfigurationProvider].GetScriptMessageRouter(); 4162 [self webViewConfigurationProvider].GetScriptMessageRouter();
4156 4163
4157 [_POSTRequestLoader loadPOSTRequest:request 4164 [_POSTRequestLoader loadPOSTRequest:request
4158 inWebView:_webView 4165 inWebView:_webView
4159 messageRouter:messageRouter 4166 messageRouter:messageRouter
4160 completionHandler:^(NSError* loadError) { 4167 completionHandler:^(NSError* loadError) {
4161 if (loadError) 4168 if (loadError)
4162 [self handleLoadError:loadError inMainFrame:YES]; 4169 [self handleLoadError:loadError
4170 inMainFrame:YES
4171 forNavigation:nil];
4163 else 4172 else
4164 self.webStateImpl->SetContentsMimeType("text/html"); 4173 self.webStateImpl->SetContentsMimeType("text/html");
4165 }]; 4174 }];
4166 } 4175 }
4167 4176
4168 - (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL { 4177 - (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL {
4169 // Remove the transient content view. 4178 // Remove the transient content view.
4170 [self clearTransientContentView]; 4179 [self clearTransientContentView];
4171 4180
4172 _loadPhase = web::LOAD_REQUESTED; 4181 _loadPhase = web::LOAD_REQUESTED;
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after
4416 : WKNavigationResponsePolicyCancel); 4425 : WKNavigationResponsePolicyCancel);
4417 } 4426 }
4418 4427
4419 // TODO(stuartmorgan): Move all the guesswork around these states out of the 4428 // TODO(stuartmorgan): Move all the guesswork around these states out of the
4420 // superclass, and wire these up to the remaining methods. 4429 // superclass, and wire these up to the remaining methods.
4421 - (void)webView:(WKWebView*)webView 4430 - (void)webView:(WKWebView*)webView
4422 didStartProvisionalNavigation:(WKNavigation*)navigation { 4431 didStartProvisionalNavigation:(WKNavigation*)navigation {
4423 [_navigationStates setState:web::WKNavigationState::STARTED 4432 [_navigationStates setState:web::WKNavigationState::STARTED
4424 forNavigation:navigation]; 4433 forNavigation:navigation];
4425 4434
4435 if (navigation &&
4436 ![[_navigationStates lastAddedNavigation] isEqual:navigation]) {
4437 // |navigation| is not the latest navigation and will be cancelled.
4438 // Ignore |navigation| as a new navigation will start soon.
4439 return;
4440 }
4441
4426 GURL webViewURL = net::GURLWithNSURL(webView.URL); 4442 GURL webViewURL = net::GURLWithNSURL(webView.URL);
4427 if (webViewURL.is_empty()) { 4443 if (webViewURL.is_empty()) {
4428 // May happen on iOS9, however in didCommitNavigation: callback the URL 4444 // May happen on iOS9, however in didCommitNavigation: callback the URL
4429 // will be "about:blank". 4445 // will be "about:blank".
4430 webViewURL = GURL(url::kAboutBlankURL); 4446 webViewURL = GURL(url::kAboutBlankURL);
4431 } 4447 }
4432 4448
4433 // Intercept renderer-initiated navigations. If this navigation has not yet 4449 // Intercept renderer-initiated navigations. If this navigation has not yet
4434 // been registered, do so. loadPhase check is necessary because 4450 // been registered, do so. loadPhase check is necessary because
4435 // lastRegisteredRequestURL may be the same as the webViewURL on a new tab 4451 // lastRegisteredRequestURL may be the same as the webViewURL on a new tab
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
4503 } 4519 }
4504 4520
4505 // Handle load cancellation for directly cancelled navigations without 4521 // Handle load cancellation for directly cancelled navigations without
4506 // handling their potential errors. Otherwise, handle the error. 4522 // handling their potential errors. Otherwise, handle the error.
4507 if ([_pendingNavigationInfo cancelled]) { 4523 if ([_pendingNavigationInfo cancelled]) {
4508 [self loadCancelled]; 4524 [self loadCancelled];
4509 } else { 4525 } else {
4510 error = WKWebViewErrorWithSource(error, PROVISIONAL_LOAD); 4526 error = WKWebViewErrorWithSource(error, PROVISIONAL_LOAD);
4511 4527
4512 if (web::IsWKWebViewSSLCertError(error)) 4528 if (web::IsWKWebViewSSLCertError(error))
4513 [self handleSSLCertError:error]; 4529 [self handleSSLCertError:error forNavigation:navigation];
4514 else 4530 else
4515 [self handleLoadError:error inMainFrame:YES]; 4531 [self handleLoadError:error inMainFrame:YES forNavigation:navigation];
4516 } 4532 }
4517 4533
4518 // This must be reset at the end, since code above may need information about 4534 // This must be reset at the end, since code above may need information about
4519 // the pending load. 4535 // the pending load.
4520 _pendingNavigationInfo.reset(); 4536 _pendingNavigationInfo.reset();
4521 _certVerificationErrors->Clear(); 4537 _certVerificationErrors->Clear();
4522 } 4538 }
4523 4539
4524 - (void)webView:(WKWebView*)webView 4540 - (void)webView:(WKWebView*)webView
4525 didCommitNavigation:(WKNavigation*)navigation { 4541 didCommitNavigation:(WKNavigation*)navigation {
4526 [_navigationStates setState:web::WKNavigationState::COMMITTED 4542 [_navigationStates setState:web::WKNavigationState::COMMITTED
4527 forNavigation:navigation]; 4543 forNavigation:navigation];
4528 4544
4529 DCHECK_EQ(_webView, webView); 4545 DCHECK_EQ(_webView, webView);
4530 _certVerificationErrors->Clear(); 4546 _certVerificationErrors->Clear();
4531 4547
4532 // This is the point where the document's URL has actually changed, and 4548 // This is the point where the document's URL has actually changed, and
4533 // pending navigation information should be applied to state information. 4549 // pending navigation information should be applied to state information.
4534 [self setDocumentURL:net::GURLWithNSURL([_webView URL])]; 4550 [self setDocumentURL:net::GURLWithNSURL([_webView URL])];
4535 4551
4536 if (!_lastRegisteredRequestURL.is_valid() && 4552 if (!_lastRegisteredRequestURL.is_valid() &&
4537 _documentURL != _lastRegisteredRequestURL) { 4553 _documentURL != _lastRegisteredRequestURL) {
4538 // if |_lastRegisteredRequestURL| is an invalid URL, then |_documentURL| 4554 // if |_lastRegisteredRequestURL| is an invalid URL, then |_documentURL|
4539 // will be "about:blank". 4555 // will be "about:blank".
4540 [[self sessionController] updatePendingItem:_documentURL]; 4556 [[self sessionController] updatePendingItem:_documentURL];
4541 } 4557 }
4542 DCHECK(_documentURL == _lastRegisteredRequestURL || 4558
4543 (!_lastRegisteredRequestURL.is_valid() && 4559 // If |navigation| is nil (which happens for windows open by DOM), then it
4560 // should be the first and the only pending navigaiton.
4561 BOOL isLastNavigation =
4562 !navigation ||
4563 [[_navigationStates lastAddedNavigation] isEqual:navigation];
4564 DCHECK(_documentURL == _lastRegisteredRequestURL || // latest navigation
4565 !isLastNavigation || // previous navigation
4566 (!_lastRegisteredRequestURL.is_valid() && // invalid URL load
4544 _documentURL.spec() == url::kAboutBlankURL)); 4567 _documentURL.spec() == url::kAboutBlankURL));
4545 4568
4546 self.webStateImpl->UpdateHttpResponseHeaders(_documentURL); 4569 self.webStateImpl->UpdateHttpResponseHeaders(_documentURL);
4547 [self commitPendingNavigationInfo]; 4570 [self commitPendingNavigationInfo];
4548 if ([self currentBackForwardListItemHolder]->navigation_type() == 4571 if ([self currentBackForwardListItemHolder]->navigation_type() ==
4549 WKNavigationTypeBackForward) { 4572 WKNavigationTypeBackForward) {
4550 // A fast back/forward won't call decidePolicyForNavigationResponse, so 4573 // A fast back/forward won't call decidePolicyForNavigationResponse, so
4551 // the MIME type needs to be updated explicitly. 4574 // the MIME type needs to be updated explicitly.
4552 NSString* storedMIMEType = 4575 NSString* storedMIMEType =
4553 [self currentBackForwardListItemHolder]->mime_type(); 4576 [self currentBackForwardListItemHolder]->mime_type();
4554 if (storedMIMEType) { 4577 if (storedMIMEType) {
4555 self.webStateImpl->SetContentsMimeType( 4578 self.webStateImpl->SetContentsMimeType(
4556 base::SysNSStringToUTF8(storedMIMEType)); 4579 base::SysNSStringToUTF8(storedMIMEType));
4557 } 4580 }
4558 } 4581 }
4559 4582
4560 // This point should closely approximate the document object change, so reset 4583 // This point should closely approximate the document object change, so reset
4561 // the list of injected scripts to those that are automatically injected. 4584 // the list of injected scripts to those that are automatically injected.
4562 _injectedScriptManagers.reset([[NSMutableSet alloc] init]); 4585 _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
4563 if ([self contentIsHTML] || self.webState->GetContentsMimeType().empty()) { 4586 if ([self contentIsHTML] || self.webState->GetContentsMimeType().empty()) {
4564 // In unit tests MIME type will be empty, because loadHTML:forURL: does not 4587 // In unit tests MIME type will be empty, because loadHTML:forURL: does not
4565 // notify web view delegate about received response, so web controller does 4588 // notify web view delegate about received response, so web controller does
4566 // not get a chance to properly update MIME type. 4589 // not get a chance to properly update MIME type.
4567 [self injectWindowID]; 4590 [self injectWindowID];
4568 } 4591 }
4569 4592
4570 [self webPageChanged]; 4593 if (isLastNavigation) {
4594 [self webPageChanged];
4595 } else {
4596 // WKWebView has more than one in progress navigaiton, and committed
kkhorimoto 2017/04/19 00:34:41 s/navigaiton/navigation
Eugene But (OOO till 7-30) 2017/04/19 01:24:36 Done.
4597 // navigation was not the latests. It is critical to keep last committed
kkhorimoto 2017/04/19 00:34:41 s/latests/latest.
Eugene But (OOO till 7-30) 2017/04/19 01:24:36 Done.
4598 // URL the same as actual document URL, so try guessing which navigation
4599 // item should be set to current.
4600 // TODO(crbug.com/712269):
4601 for (int i = 0; i < self.navigationManagerImpl->GetItemCount(); i++) {
4602 web::NavigationItem* item = self.navigationManagerImpl->GetItemAtIndex(i);
4603 if (item->GetURL() == _documentURL) {
4604 // Do not discard pending entry, because another pending navigation is
4605 // still in progress and will commit or fail soon.
4606 [self.sessionController goToItemAtIndex:i discardNonCommittedItems:NO];
4607 }
4608 }
4609 }
4571 self.webStateImpl->OnNavigationCommitted(_documentURL); 4610 self.webStateImpl->OnNavigationCommitted(_documentURL);
4572 4611
4573 [self updateSSLStatusForCurrentNavigationItem]; 4612 [self updateSSLStatusForCurrentNavigationItem];
4574 4613
4575 // Attempt to update the HTML5 history state. 4614 // Attempt to update the HTML5 history state.
4576 [self updateHTML5HistoryState]; 4615 [self updateHTML5HistoryState];
4577 4616
4578 // This is the point where pending entry has been committed, and navigation 4617 // This is the point where pending entry has been committed, and navigation
4579 // item title should be updated. 4618 // item title should be updated.
4580 [self setNavigationItemTitle:[_webView title]]; 4619 [self setNavigationItemTitle:[_webView title]];
(...skipping 11 matching lines...) Expand all
4592 didFinishNavigation:(WKNavigation*)navigation { 4631 didFinishNavigation:(WKNavigation*)navigation {
4593 [_navigationStates setState:web::WKNavigationState::FINISHED 4632 [_navigationStates setState:web::WKNavigationState::FINISHED
4594 forNavigation:navigation]; 4633 forNavigation:navigation];
4595 4634
4596 DCHECK(!_isHalted); 4635 DCHECK(!_isHalted);
4597 // Trigger JavaScript driven post-document-load-completion tasks. 4636 // Trigger JavaScript driven post-document-load-completion tasks.
4598 // TODO(crbug.com/546350): Investigate using 4637 // TODO(crbug.com/546350): Investigate using
4599 // WKUserScriptInjectionTimeAtDocumentEnd to inject this material at the 4638 // WKUserScriptInjectionTimeAtDocumentEnd to inject this material at the
4600 // appropriate time rather than invoking here. 4639 // appropriate time rather than invoking here.
4601 web::ExecuteJavaScript(webView, @"__gCrWeb.didFinishNavigation()", nil); 4640 web::ExecuteJavaScript(webView, @"__gCrWeb.didFinishNavigation()", nil);
4602 [self didFinishNavigation]; 4641 [self didFinishNavigation:navigation];
4603 } 4642 }
4604 4643
4605 - (void)webView:(WKWebView*)webView 4644 - (void)webView:(WKWebView*)webView
4606 didFailNavigation:(WKNavigation*)navigation 4645 didFailNavigation:(WKNavigation*)navigation
4607 withError:(NSError*)error { 4646 withError:(NSError*)error {
4608 [_navigationStates setState:web::WKNavigationState::FAILED 4647 [_navigationStates setState:web::WKNavigationState::FAILED
4609 forNavigation:navigation]; 4648 forNavigation:navigation];
4610 4649
4611 [self handleLoadError:WKWebViewErrorWithSource(error, NAVIGATION) 4650 [self handleLoadError:WKWebViewErrorWithSource(error, NAVIGATION)
4612 inMainFrame:YES]; 4651 inMainFrame:YES
4652 forNavigation:navigation];
4613 _certVerificationErrors->Clear(); 4653 _certVerificationErrors->Clear();
4614 } 4654 }
4615 4655
4616 - (void)webView:(WKWebView*)webView 4656 - (void)webView:(WKWebView*)webView
4617 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge 4657 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
4618 completionHandler: 4658 completionHandler:
4619 (void (^)(NSURLSessionAuthChallengeDisposition, 4659 (void (^)(NSURLSessionAuthChallengeDisposition,
4620 NSURLCredential*))completionHandler { 4660 NSURLCredential*))completionHandler {
4621 NSString* authMethod = challenge.protectionSpace.authenticationMethod; 4661 NSString* authMethod = challenge.protectionSpace.authenticationMethod;
4622 if ([authMethod isEqual:NSURLAuthenticationMethodHTTPBasic] || 4662 if ([authMethod isEqual:NSURLAuthenticationMethodHTTPBasic] ||
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
4762 } else { 4802 } else {
4763 _webStateImpl->OnNavigationCommitted(webViewURL); 4803 _webStateImpl->OnNavigationCommitted(webViewURL);
4764 } 4804 }
4765 } 4805 }
4766 4806
4767 [self updateSSLStatusForCurrentNavigationItem]; 4807 [self updateSSLStatusForCurrentNavigationItem];
4768 4808
4769 // Fast back forward navigation may not call |didFinishNavigation:|, so 4809 // Fast back forward navigation may not call |didFinishNavigation:|, so
4770 // signal did finish navigation explicitly. 4810 // signal did finish navigation explicitly.
4771 if (_lastRegisteredRequestURL == _documentURL) { 4811 if (_lastRegisteredRequestURL == _documentURL) {
4772 [self didFinishNavigation]; 4812 [self didFinishNavigation:nil];
4773 } 4813 }
4774 } 4814 }
4775 4815
4776 - (void)webViewTitleDidChange { 4816 - (void)webViewTitleDidChange {
4777 // WKWebView's title becomes empty when the web process dies; ignore that 4817 // WKWebView's title becomes empty when the web process dies; ignore that
4778 // update. 4818 // update.
4779 if (_webProcessIsDead) { 4819 if (_webProcessIsDead) {
4780 DCHECK_EQ([_webView title].length, 0U); 4820 DCHECK_EQ([_webView title].length, 0U);
4781 return; 4821 return;
4782 } 4822 }
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after
4909 if (![self isLoadRequestPendingForURL:newURL]) 4949 if (![self isLoadRequestPendingForURL:newURL])
4910 [self registerLoadRequest:newURL]; 4950 [self registerLoadRequest:newURL];
4911 } 4951 }
4912 4952
4913 [self setDocumentURL:newURL]; 4953 [self setDocumentURL:newURL];
4914 4954
4915 if (!_changingHistoryState) { 4955 if (!_changingHistoryState) {
4916 [self didStartLoadingURL:_documentURL]; 4956 [self didStartLoadingURL:_documentURL];
4917 _webStateImpl->OnSameDocumentNavigation(newURL); 4957 _webStateImpl->OnSameDocumentNavigation(newURL);
4918 [self updateSSLStatusForCurrentNavigationItem]; 4958 [self updateSSLStatusForCurrentNavigationItem];
4919 [self didFinishNavigation]; 4959 [self didFinishNavigation:nil];
4920 } 4960 }
4921 } 4961 }
4922 4962
4923 - (BOOL)isLoadRequestPendingForURL:(const GURL&)targetURL { 4963 - (BOOL)isLoadRequestPendingForURL:(const GURL&)targetURL {
4924 if (self.loadPhase != web::LOAD_REQUESTED) 4964 if (self.loadPhase != web::LOAD_REQUESTED)
4925 return NO; 4965 return NO;
4926 4966
4927 web::NavigationItem* pendingItem = 4967 web::NavigationItem* pendingItem =
4928 self.webState->GetNavigationManager()->GetPendingItem(); 4968 self.webState->GetNavigationManager()->GetPendingItem();
4929 return pendingItem && pendingItem->GetURL() == targetURL; 4969 return pendingItem && pendingItem->GetURL() == targetURL;
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
5124 - (void)simulateLoadRequestWithURL:(const GURL&)URL { 5164 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
5125 _lastRegisteredRequestURL = URL; 5165 _lastRegisteredRequestURL = URL;
5126 _loadPhase = web::LOAD_REQUESTED; 5166 _loadPhase = web::LOAD_REQUESTED;
5127 } 5167 }
5128 5168
5129 - (NSString*)referrerFromNavigationAction:(WKNavigationAction*)action { 5169 - (NSString*)referrerFromNavigationAction:(WKNavigationAction*)action {
5130 return [action.request valueForHTTPHeaderField:kReferrerHeaderName]; 5170 return [action.request valueForHTTPHeaderField:kReferrerHeaderName];
5131 } 5171 }
5132 5172
5133 @end 5173 @end
OLDNEW
« no previous file with comments | « ios/web/navigation/navigation_manager_impl_unittest.mm ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698