| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| |
| #import <WebKit/WebKit.h> |
| |
| #import <objc/runtime.h> |
| #include <stddef.h> |
| |
| #include <cmath> |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/containers/mru_cache.h" |
| #include "base/feature_list.h" |
| #import "base/ios/block_types.h" |
| #include "base/ios/ios_util.h" |
| #import "base/ios/ns_error_util.h" |
| #include "base/json/string_escape.h" |
| #include "base/logging.h" |
| #include "base/mac/bundle_locations.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "base/values.h" |
| #include "crypto/symmetric_key.h" |
| #import "ios/net/http_response_headers_util.h" |
| #include "ios/web/history_state_util.h" |
| #import "ios/web/interstitials/web_interstitial_impl.h" |
| #import "ios/web/navigation/crw_navigation_item_holder.h" |
| #import "ios/web/navigation/crw_session_controller.h" |
| #include "ios/web/navigation/error_retry_state_machine.h" |
| #import "ios/web/navigation/navigation_item_impl.h" |
| #import "ios/web/navigation/navigation_manager_impl.h" |
| #include "ios/web/navigation/navigation_manager_util.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #include "ios/web/net/cert_host_pair.h" |
| #import "ios/web/net/crw_cert_verification_controller.h" |
| #import "ios/web/net/crw_ssl_status_updater.h" |
| #include "ios/web/public/browser_state.h" |
| #import "ios/web/public/download/download_controller.h" |
| #include "ios/web/public/favicon_url.h" |
| #include "ios/web/public/features.h" |
| #import "ios/web/public/java_script_dialog_presenter.h" |
| #import "ios/web/public/navigation_item.h" |
| #import "ios/web/public/navigation_manager.h" |
| #import "ios/web/public/origin_util.h" |
| #include "ios/web/public/referrer.h" |
| #include "ios/web/public/referrer_util.h" |
| #include "ios/web/public/ssl_status.h" |
| #import "ios/web/public/url_scheme_util.h" |
| #include "ios/web/public/url_util.h" |
| #import "ios/web/public/web_client.h" |
| #include "ios/web/public/web_kit_constants.h" |
| #import "ios/web/public/web_state/context_menu_params.h" |
| #import "ios/web/public/web_state/js/crw_js_injection_manager.h" |
| #import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
| #import "ios/web/public/web_state/page_display_state.h" |
| #include "ios/web/public/web_state/session_certificate_policy_cache.h" |
| #import "ios/web/public/web_state/ui/crw_content_view.h" |
| #import "ios/web/public/web_state/ui/crw_context_menu_delegate.h" |
| #import "ios/web/public/web_state/ui/crw_native_content.h" |
| #import "ios/web/public/web_state/ui/crw_native_content_provider.h" |
| #import "ios/web/public/web_state/ui/crw_web_view_content_view.h" |
| #import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h" |
| #include "ios/web/public/web_state/url_verification_constants.h" |
| #include "ios/web/public/web_state/web_frame.h" |
| #include "ios/web/public/web_state/web_frame_util.h" |
| #import "ios/web/public/web_state/web_state.h" |
| #include "ios/web/public/web_state/web_state_interface_provider.h" |
| #import "ios/web/public/web_state/web_state_policy_decider.h" |
| #include "ios/web/public/webui/web_ui_ios.h" |
| #import "ios/web/web_state/error_translation_util.h" |
| #import "ios/web/web_state/js/crw_js_post_request_loader.h" |
| #import "ios/web/web_state/js/crw_js_window_id_manager.h" |
| #import "ios/web/web_state/navigation_context_impl.h" |
| #import "ios/web/web_state/page_viewport_state.h" |
| #import "ios/web/web_state/ui/crw_context_menu_controller.h" |
| #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h" |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| #import "ios/web/web_state/ui/crw_web_controller_container_view.h" |
| #import "ios/web/web_state/ui/crw_web_view_navigation_proxy.h" |
| #import "ios/web/web_state/ui/crw_web_view_proxy_impl.h" |
| #import "ios/web/web_state/ui/crw_wk_navigation_states.h" |
| #import "ios/web/web_state/ui/crw_wk_script_message_router.h" |
| #import "ios/web/web_state/ui/favicon_util.h" |
| #import "ios/web/web_state/ui/wk_back_forward_list_item_holder.h" |
| #import "ios/web/web_state/ui/wk_navigation_action_util.h" |
| #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h" |
| #import "ios/web/web_state/web_frame_impl.h" |
| #import "ios/web/web_state/web_frames_manager_impl.h" |
| #import "ios/web/web_state/web_state_impl.h" |
| #import "ios/web/web_state/web_view_internal_creation_util.h" |
| #import "ios/web/web_state/wk_web_view_security_util.h" |
| #import "ios/web/webui/crw_web_ui_manager.h" |
| #import "ios/web/webui/mojo_facade.h" |
| #import "net/base/mac/url_conversions.h" |
| #include "net/base/net_errors.h" |
| #include "net/cert/x509_util_ios.h" |
| #include "net/ssl/ssl_info.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using web::NavigationManager; |
| using web::NavigationManagerImpl; |
| using web::WebState; |
| using web::WebStateImpl; |
| |
| namespace { |
| |
| using web::wk_navigation_util::IsPlaceholderUrl; |
| using web::wk_navigation_util::CreatePlaceholderUrlForUrl; |
| using web::wk_navigation_util::ExtractUrlFromPlaceholderUrl; |
| using web::wk_navigation_util::IsRestoreSessionUrl; |
| using web::wk_navigation_util::IsWKInternalUrl; |
| |
| // Struct to capture data about a user interaction. Records the time of the |
| // interaction and the main document URL at that time. |
| struct UserInteractionEvent { |
| UserInteractionEvent(GURL url) |
| : main_document_url(url), time(CFAbsoluteTimeGetCurrent()) {} |
| // Main document URL at the time the interaction occurred. |
| GURL main_document_url; |
| // Time that the interaction occurred, measured in seconds since Jan 1 2001. |
| CFAbsoluteTime time; |
| }; |
| |
| // Keys for JavaScript command handlers context. |
| NSString* const kUserIsInteractingKey = @"userIsInteracting"; |
| NSString* const kOriginURLKey = @"originURL"; |
| NSString* const kIsMainFrame = @"isMainFrame"; |
| |
| // URL scheme for messages sent from javascript for asynchronous processing. |
| NSString* const kScriptMessageName = @"crwebinvoke"; |
| |
| // Message command sent when a frame becomes available. |
| NSString* const kFrameBecameAvailableMessageName = @"FrameBecameAvailable"; |
| // Message command sent when a frame is unloading. |
| NSString* const kFrameBecameUnavailableMessageName = @"FrameBecameUnavailable"; |
| |
| // Values for the histogram that counts slow/fast back/forward navigations. |
| enum class BackForwardNavigationType { |
| // Fast back navigation through WKWebView back-forward list. |
| FAST_BACK = 0, |
| // Slow back navigation when back-forward list navigation is not possible. |
| SLOW_BACK = 1, |
| // Fast forward navigation through WKWebView back-forward list. |
| FAST_FORWARD = 2, |
| // Slow forward navigation when back-forward list navigation is not possible. |
| SLOW_FORWARD = 3, |
| BACK_FORWARD_NAVIGATION_TYPE_COUNT |
| }; |
| |
| // Represents cert verification error, which happened inside |
| // |webView:didReceiveAuthenticationChallenge:completionHandler:| and should |
| // be checked inside |webView:didFailProvisionalNavigation:withError:|. |
| struct CertVerificationError { |
| CertVerificationError(BOOL is_recoverable, net::CertStatus status) |
| : is_recoverable(is_recoverable), status(status) {} |
| |
| BOOL is_recoverable; |
| net::CertStatus status; |
| }; |
| |
| // Type of Cache object for storing cert verification errors. |
| typedef base::MRUCache<web::CertHostPair, CertVerificationError> |
| CertVerificationErrorsCacheType; |
| |
| // Maximum number of errors to store in cert verification errors cache. |
| // Cache holds errors only for pending navigations, so the actual number of |
| // stored errors is not expected to be high. |
| const CertVerificationErrorsCacheType::size_type kMaxCertErrorsCount = 100; |
| |
| } // namespace |
| |
| #pragma mark - |
| |
| // A container object for any navigation information that is only available |
| // during pre-commit delegate callbacks, and thus must be held until the |
| // navigation commits and the informatino can be used. |
| @interface CRWWebControllerPendingNavigationInfo : NSObject { |
| } |
| // The referrer for the page. |
| @property(nonatomic, copy) NSString* referrer; |
| // The MIME type for the page. |
| @property(nonatomic, copy) NSString* MIMEType; |
| // The navigation type for the load. |
| @property(nonatomic, assign) WKNavigationType navigationType; |
| // HTTP request method for the load. |
| @property(nonatomic, copy) NSString* HTTPMethod; |
| // Whether the pending navigation has been directly cancelled before the |
| // navigation is committed. |
| // Cancelled navigations should be simply discarded without handling any |
| // specific error. |
| @property(nonatomic, assign) BOOL cancelled; |
| // Whether the navigation was initiated by a user gesture. |
| @property(nonatomic, assign) BOOL hasUserGesture; |
| |
| @end |
| |
| @implementation CRWWebControllerPendingNavigationInfo |
| @synthesize referrer = _referrer; |
| @synthesize MIMEType = _MIMEType; |
| @synthesize navigationType = _navigationType; |
| @synthesize HTTPMethod = _HTTPMethod; |
| @synthesize cancelled = _cancelled; |
| @synthesize hasUserGesture = _hasUserGesture; |
| |
| - (instancetype)init { |
| if ((self = [super init])) { |
| _navigationType = WKNavigationTypeOther; |
| } |
| return self; |
| } |
| |
| @end |
| |
| @interface CRWWebController ()<CRWContextMenuDelegate, |
| CRWNativeContentDelegate, |
| CRWSSLStatusUpdaterDataSource, |
| CRWSSLStatusUpdaterDelegate, |
| CRWWebControllerContainerViewDelegate, |
| CRWWebViewScrollViewProxyObserver, |
| WKNavigationDelegate, |
| WKUIDelegate> { |
| // The WKWebView managed by this instance. |
| WKWebView* _webView; |
| // The view used to display content. Must outlive |_webViewProxy|. The |
| // container view should be accessed through this property rather than |
| // |self.view| from within this class, as |self.view| triggers creation while |
| // |self.containerView| will return nil if the view hasn't been instantiated. |
| CRWWebControllerContainerView* _containerView; |
| // YES if the current URL load was triggered in Web Controller. NO by default |
| // and after web usage was disabled. Used by |-loadCurrentURLIfNecessary| to |
| // prevent extra loads. |
| BOOL _currentURLLoadWasTrigerred; |
| // If |_contentView| contains a native view rather than a web view, this |
| // is its controller. If it's a web view, this is nil. |
| id<CRWNativeContent> _nativeController; |
| BOOL _isHalted; // YES if halted. Halting happens prior to destruction. |
| BOOL _isBeingDestroyed; // YES if in the process of closing. |
| // YES if a user interaction has been registered at any time once the page has |
| // loaded. |
| BOOL _userInteractionRegistered; |
| // YES if the user has interacted with the content area since the last URL |
| // change. |
| BOOL _interactionRegisteredSinceLastURLChange; |
| // The actual URL of the document object (i.e., the last committed URL). |
| // TODO(crbug.com/549616): Remove this in favor of just updating the |
| // navigation manager and treating that as authoritative. |
| GURL _documentURL; |
| // Page loading phase. |
| web::LoadPhase _loadPhase; |
| // The web::PageDisplayState recorded when the page starts loading. |
| web::PageDisplayState _displayStateOnStartLoading; |
| // Whether or not the page has zoomed since the current navigation has been |
| // committed, either by user interaction or via |-restoreStateFromHistory|. |
| BOOL _pageHasZoomed; |
| // Whether a PageDisplayState is currently being applied. |
| BOOL _applyingPageState; |
| // Actions to execute once the page load is complete. |
| NSMutableArray* _pendingLoadCompleteActions; |
| // UIGestureRecognizers to add to the web view. |
| NSMutableArray* _gestureRecognizers; |
| // Flag to say if browsing is enabled. |
| BOOL _webUsageEnabled; |
| // The touch tracking recognizer allowing us to decide if a navigation is |
| // started by the user. |
| CRWTouchTrackingRecognizer* _touchTrackingRecognizer; |
| // The controller that tracks long press and check context menu trigger. |
| CRWContextMenuController* _contextMenuController; |
| // Whether a click is in progress. |
| BOOL _clickInProgress; |
| // Data on the recorded last user interaction. |
| std::unique_ptr<UserInteractionEvent> _lastUserInteraction; |
| // YES if there has been user interaction with views owned by this controller. |
| BOOL _userInteractedWithWebController; |
| // The time of the last page transfer start, measured in seconds since Jan 1 |
| // 2001. |
| CFAbsoluteTime _lastTransferTimeInSeconds; |
| // Default URL (about:blank). |
| GURL _defaultURL; |
| // Whether the web page is currently performing window.history.pushState or |
| // window.history.replaceState |
| // Set to YES on window.history.willChangeState message. To NO on |
| // window.history.didPushState or window.history.didReplaceState. |
| BOOL _changingHistoryState; |
| // Set to YES when a hashchange event is manually dispatched for same-document |
| // history navigations. |
| BOOL _dispatchingSameDocumentHashChangeEvent; |
| |
| // Object for loading POST requests with body. |
| CRWJSPOSTRequestLoader* _POSTRequestLoader; |
| |
| // WebStateImpl instance associated with this CRWWebController, web controller |
| // does not own this pointer. |
| WebStateImpl* _webStateImpl; |
| |
| // A set of script managers whose scripts have been injected into the current |
| // page. |
| // TODO(stuartmorgan): Revisit this approach; it's intended only as a stopgap |
| // measure to make all the existing script managers work. Longer term, there |
| // should probably be a couple of points where managers can register to have |
| // things happen automatically based on page lifecycle, and if they don't want |
| // to use one of those fixed points, they should make their scripts internally |
| // idempotent. |
| NSMutableSet* _injectedScriptManagers; |
| |
| // Script manager for setting the windowID. |
| CRWJSWindowIDManager* _windowIDJSManager; |
| |
| // The receiver of JavaScripts. |
| CRWJSInjectionReceiver* _jsInjectionReceiver; |
| |
| // Backs up property with the same name. |
| std::unique_ptr<web::MojoFacade> _mojoFacade; |
| |
| // Referrer for the current page; does not include the fragment. |
| NSString* _currentReferrerString; |
| |
| // Pending information for an in-progress page navigation. The lifetime of |
| // this object starts at |decidePolicyForNavigationAction| where the info is |
| // extracted from the request, and ends at either |didCommitNavigation| or |
| // |didFailProvisionalNavigation|. |
| CRWWebControllerPendingNavigationInfo* _pendingNavigationInfo; |
| |
| // Holds all WKNavigation objects and their states which are currently in |
| // flight. |
| CRWWKNavigationStates* _navigationStates; |
| |
| // The WKNavigation captured when |stopLoading| was called. Used for reporting |
| // WebController.EmptyNavigationManagerCausedByStopLoading UMA metric which |
| // helps with diagnosing a navigation related crash (crbug.com/565457). |
| __weak WKNavigation* _stoppedWKNavigation; |
| |
| // Used to poll for a SafeBrowsing warning being displayed. This is created in |
| // |decidePolicyForNavigationAction| and destroyed once any of the following |
| // happens: 1) a SafeBrowsing warning is detected; 2) any WKNavigationDelegate |
| // method is called; 3) |abortLoad| is called. |
| base::RepeatingTimer _safeBrowsingWarningDetectionTimer; |
| |
| // CRWWebUIManager object for loading WebUI pages. |
| CRWWebUIManager* _webUIManager; |
| |
| // Updates SSLStatus for current navigation item. |
| CRWSSLStatusUpdater* _SSLStatusUpdater; |
| |
| // Controller used for certs verification to help with blocking requests with |
| // bad SSL cert, presenting SSL interstitials and determining SSL status for |
| // Navigation Items. |
| CRWCertVerificationController* _certVerificationController; |
| |
| // CertVerification errors which happened inside |
| // |webView:didReceiveAuthenticationChallenge:completionHandler:|. |
| // Key is leaf-cert/host pair. This storage is used to carry calculated |
| // cert status from |didReceiveAuthenticationChallenge:| to |
| // |didFailProvisionalNavigation:| delegate method. |
| std::unique_ptr<CertVerificationErrorsCacheType> _certVerificationErrors; |
| } |
| |
| // If |contentView_| contains a web view, this is the web view it contains. |
| // If not, it's nil. |
| @property(weak, nonatomic, readonly) WKWebView* webView; |
| // The scroll view of |webView|. |
| @property(weak, nonatomic, readonly) UIScrollView* webScrollView; |
| // The current page state of the web view. Writing to this property |
| // asynchronously applies the passed value to the current web view. |
| @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState; |
| // The currently displayed native controller, if any. |
| @property(weak, nonatomic, readwrite) id<CRWNativeContent> nativeController; |
| // Returns NavigationManager's session controller. |
| @property(weak, nonatomic, readonly) CRWSessionController* sessionController; |
| // The associated NavigationManagerImpl. |
| @property(nonatomic, readonly) NavigationManagerImpl* navigationManagerImpl; |
| // Whether the associated WebState has an opener. |
| @property(nonatomic, readonly) BOOL hasOpener; |
| // Dictionary where keys are the names of WKWebView properties and values are |
| // selector names which should be called when a corresponding property has |
| // changed. e.g. @{ @"URL" : @"webViewURLDidChange" } means that |
| // -[self webViewURLDidChange] must be called every time when WKWebView.URL is |
| // changed. |
| @property(weak, nonatomic, readonly) NSDictionary* WKWebViewObservers; |
| |
| // The web view's view of the current URL. During page transitions |
| // this may not be the same as the session history's view of the current URL. |
| // This method can change the state of the CRWWebController, as it will display |
| // an error if the returned URL is not reliable from a security point of view. |
| // Note that this method is expensive, so it should always be cached locally if |
| // it's needed multiple times in a method. |
| @property(nonatomic, readonly) GURL currentURL; |
| // Returns the referrer for the current page. |
| @property(nonatomic, readonly) web::Referrer currentReferrer; |
| |
| // Returns YES if the user interacted with the page recently. |
| @property(nonatomic, readonly) BOOL userClickedRecently; |
| |
| // User agent type of the transient item if any, the pending item if a |
| // navigation is in progress or the last committed item otherwise. |
| // Returns MOBILE, the default type, if navigation manager is nullptr or empty. |
| @property(nonatomic, readonly) web::UserAgentType userAgentType; |
| |
| // Facade for Mojo API. |
| @property(nonatomic, readonly) web::MojoFacade* mojoFacade; |
| |
| // TODO(crbug.com/692871): Remove these functions and replace with more |
| // appropriate NavigationItem getters. |
| // Returns the navigation item for the current page. |
| @property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem; |
| // Returns the current transition type. |
| @property(nonatomic, readonly) ui::PageTransition currentTransition; |
| // Returns the referrer for current navigation item. May be empty. |
| @property(nonatomic, readonly) web::Referrer currentNavItemReferrer; |
| // The HTTP headers associated with the current navigation item. These are nil |
| // unless the request was a POST. |
| @property(weak, nonatomic, readonly) NSDictionary* currentHTTPHeaders; |
| |
| // YES if a user interaction has been registered at any time since the page has |
| // loaded. |
| @property(nonatomic, readwrite) BOOL userInteractionRegistered; |
| |
| // Called when the web page has changed document and/or URL, and so the page |
| // navigation should be reported to the delegate, and internal state updated to |
| // reflect the fact that the navigation has occurred. |context| contains |
| // information about the navigation that triggered the document/URL change. |
| // TODO(stuartmorgan): The code conflates URL changes and document object |
| // changes; the two need to be separated and handled differently. |
| - (void)webPageChangedWithContext:(const web::NavigationContextImpl*)context; |
| // Resets any state that is associated with a specific document object (e.g., |
| // page interaction tracking). |
| - (void)resetDocumentSpecificState; |
| // Called when a page (native or web) has actually started loading (i.e., for |
| // a web page the document has actually changed), or after the load request has |
| // been registered for a non-document-changing URL change. Updates internal |
| // state not specific to web pages. |
| - (void)didStartLoading; |
| // Returns YES if the URL looks like it is one CRWWebController can show. |
| + (BOOL)webControllerCanShow:(const GURL&)url; |
| // Returns a lazily created CRWTouchTrackingRecognizer. |
| - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer; |
| // Creates a container view if it's not yet created. |
| - (void)ensureContainerViewCreated; |
| // Creates a web view if it's not yet created. |
| - (void)ensureWebViewCreated; |
| // Creates a web view with given |config|. No-op if web view is already created. |
| - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config; |
| // Returns a new autoreleased web view created with given configuration. |
| - (WKWebView*)webViewWithConfiguration:(WKWebViewConfiguration*)config; |
| // Sets the value of the webView property, and performs its basic setup. |
| - (void)setWebView:(WKWebView*)webView; |
| // Wraps the web view in a CRWWebViewContentView and adds it to the container |
| // view. |
| - (void)displayWebView; |
| // Called when web view process has been terminated. |
| - (void)webViewWebProcessDidCrash; |
| // Returns the WKWebViewConfigurationProvider associated with the web |
| // controller's BrowserState. |
| - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider; |
| // Extracts "Referer" [sic] value from WKNavigationAction request header. |
| - (NSString*)referrerFromNavigationAction:(WKNavigationAction*)action; |
| |
| // Returns the current URL of the web view, and sets |trustLevel| accordingly |
| // based on the confidence in the verification. |
| - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel; |
| // Returns |YES| if |url| should be loaded in a native view. |
| - (BOOL)shouldLoadURLInNativeView:(const GURL&)url; |
| // 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. |
| // This is necessary because WKWebView ignores POST request body. |
| // Workaround for https://bugs.webkit.org/show_bug.cgi?id=145410 |
| // TODO(crbug.com/740987): Remove |loadPOSTRequest:| workaround once iOS 10 is |
| // dropped. |
| - (WKNavigation*)loadPOSTRequest:(NSMutableURLRequest*)request; |
| // Loads the HTML into the page at the given URL. |
| - (void)loadHTML:(NSString*)html forURL:(const GURL&)url; |
| |
| // Extracts navigation info from WKNavigationAction and sets it as a pending. |
| // Some pieces of navigation information are only known in |
| // |decidePolicyForNavigationAction|, but must be in a pending state until |
| // |didgo/Navigation| where it becames current. |
| - (void)updatePendingNavigationInfoFromNavigationAction: |
| (WKNavigationAction*)action; |
| // Extracts navigation info from WKNavigationResponse and sets it as a pending. |
| // Some pieces of navigation information are only known in |
| // |decidePolicyForNavigationResponse|, but must be in a pending state until |
| // |didCommitNavigation| where it becames current. |
| - (void)updatePendingNavigationInfoFromNavigationResponse: |
| (WKNavigationResponse*)response; |
| // Updates current state with any pending information. Should be called when a |
| // navigation is committed. |
| - (void)commitPendingNavigationInfo; |
| // Returns a NSMutableURLRequest that represents the current NavigationItem. |
| - (NSMutableURLRequest*)requestForCurrentNavigationItem; |
| // Returns the WKBackForwardListItemHolder for the current navigation item. |
| - (web::WKBackForwardListItemHolder*)currentBackForwardListItemHolder; |
| // Updates the WKBackForwardListItemHolder navigation item. |
| - (void)updateCurrentBackForwardListItemHolder; |
| |
| // Presents native content using the native controller for |item| without |
| // notifying WebStateObservers. This method does not modify the underlying web |
| // view. It simply covers the web view with the native content. |
| // |-didLoadNativeContentForNavigationItem| must be called some time later |
| // to notify WebStateObservers. |
| - (void)presentNativeContentForNavigationItem:(web::NavigationItem*)item; |
| // Notifies WebStateObservers the completion of this navigation. |
| - (void)didLoadNativeContentForNavigationItem:(web::NavigationItemImpl*)item; |
| // Loads a blank page directly into WKWebView as a placeholder for a Native View |
| // or WebUI URL. This page has the URL about:blank?for=<encoded original URL>. |
| // If |originalContext| is provided, reuse it for the placeholder navigation |
| // instead of creating a new one. See "Handling App-specific URLs" |
| // section of go/bling-navigation-experiment for details. |
| - (web::NavigationContextImpl*) |
| loadPlaceholderInWebViewForURL:(const GURL&)originalURL |
| forContext:(std::unique_ptr<web::NavigationContextImpl>) |
| originalContext; |
| // Executes the command specified by the ErrorRetryStateMachine. |
| - (void)handleErrorRetryCommand:(web::ErrorRetryCommand)command |
| navigationItem:(web::NavigationItemImpl*)item |
| navigationContext:(web::NavigationContextImpl*)context |
| originalNavigation:(WKNavigation*)originalNavigation; |
| // Loads the error page. |
| - (void)loadErrorPageForNavigationItem:(web::NavigationItemImpl*)item |
| navigationContext:(web::NavigationContextImpl*)context; |
| // Aborts any load for both the web view and web controller. |
| - (void)abortLoad; |
| // Updates the internal state and informs the delegate that any outstanding load |
| // operations are cancelled. |
| - (void)loadCancelled; |
| // If YES, the page should be closed if it successfully redirects to a native |
| // application, for example if a new tab redirects to the App Store. |
| - (BOOL)shouldClosePageOnNativeApplicationLoad; |
| // Discards non committed items, only if the last committed URL was not loaded |
| // in native view. But if it was a native view, no discard will happen to avoid |
| // an ugly animation where the web view is inserted and quickly removed. |
| - (void)discardNonCommittedItemsIfLastCommittedWasNotNativeView; |
| // Updates URL for navigation context and navigation item. |
| - (void)didReceiveRedirectForNavigation:(web::NavigationContextImpl*)context |
| withURL:(const GURL&)URL; |
| // Called following navigation completion to generate final navigation lifecycle |
| // events. Navigation is considered complete when the document has finished |
| // loading, or when other page load mechanics are completed on a |
| // non-document-changing URL change. |
| - (void)didFinishNavigation:(web::NavigationContextImpl*)context; |
| // Update the appropriate parts of the model and broadcast to the embedder. This |
| // may be called multiple times and thus must be idempotent. |
| - (void)loadCompleteWithSuccess:(BOOL)loadSuccess |
| forContext:(web::NavigationContextImpl*)context; |
| // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED. |
| // |context| contains information about the navigation associated with the URL. |
| // It is nil if currentURL is invalid. |
| - (void)didFinishWithURL:(const GURL&)currentURL |
| loadSuccess:(BOOL)loadSuccess |
| context:(nullable const web::NavigationContextImpl*)context; |
| // Navigates forwards or backwards by |delta| pages. No-op if delta is out of |
| // bounds. Reloads if delta is 0. |
| // TODO(crbug.com/661316): Move this method to NavigationManager. |
| - (void)rendererInitiatedGoDelta:(int)delta hasUserGesture:(BOOL)hasUserGesture; |
| // Informs the native controller if web usage is allowed or not. |
| - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled; |
| // Acts on a single message from the JS object, parsed from JSON into a |
| // DictionaryValue. Returns NO if the format for the message was invalid. |
| - (BOOL)respondToMessage:(base::DictionaryValue*)crwMessage |
| userIsInteracting:(BOOL)userIsInteracting |
| originURL:(const GURL&)originURL |
| isMainFrame:(BOOL)isMainFrame |
| senderFrame:(web::WebFrame*)senderFrame; |
| // Called when web controller receives a new message from the web page. |
| - (void)didReceiveScriptMessage:(WKScriptMessage*)message; |
| // Returns a new script which wraps |script| with windowID check so |script| is |
| // not evaluated on windowID mismatch. |
| - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script; |
| // Attempts to handle a script message. Returns YES on success, NO otherwise. |
| - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage; |
| // Handles frame became available message. |
| - (void)frameBecameAvailableWithMessage:(WKScriptMessage*)message; |
| // Handles frame became unavailable message. |
| - (void)frameBecameUnavailableWithMessage:(WKScriptMessage*)message; |
| // Clears the frames list. |
| - (void)removeAllWebFrames; |
| // Registers load request with empty referrer and link or client redirect |
| // transition based on user interaction state. Returns navigation context for |
| // this request. |
| - (std::unique_ptr<web::NavigationContextImpl>) |
| registerLoadRequestForURL:(const GURL&)URL |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation |
| hasUserGesture:(BOOL)hasUserGesture |
| placeholderNavigation:(BOOL)placeholderNavigation; |
| // Prepares web controller and delegates for anticipated page change. |
| // Allows several methods to invoke webWill/DidAddPendingURL on anticipated page |
| // change, using the same cached request and calculated transition types. |
| // Returns navigation context for this request. |
| - (std::unique_ptr<web::NavigationContextImpl>) |
| registerLoadRequestForURL:(const GURL&)URL |
| referrer:(const web::Referrer&)referrer |
| transition:(ui::PageTransition)transition |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation |
| hasUserGesture:(BOOL)hasUserGesture |
| placeholderNavigation:(BOOL)placeholderNavigation; |
| // Maps WKNavigationType to ui::PageTransition. |
| - (ui::PageTransition)pageTransitionFromNavigationType: |
| (WKNavigationType)navigationType; |
| // Updates the HTML5 history state of the page using the current NavigationItem. |
| // For same-document navigations and navigations affected by |
| // window.history.[push/replace]State(), the URL and serialized state object |
| // will be updated to the current NavigationItem's values. A popState event |
| // will be triggered for all same-document navigations. Additionally, a |
| // hashchange event will be triggered for same-document navigations where the |
| // only difference between the current and previous URL is the fragment. |
| - (void)updateHTML5HistoryState; |
| // Generates the JavaScript string used to update the UIWebView's URL so that it |
| // matches the URL displayed in the omnibox and sets window.history.state to |
| // stateObject. Needed for history.pushState() and history.replaceState(). |
| - (NSString*)javaScriptToReplaceWebViewURL:(const GURL&)URL |
| stateObjectJSON:(NSString*)stateObject; |
| // Generates the JavaScript string used to manually dispatch a popstate event, |
| // using |stateObjectJSON| as the event parameter. |
| - (NSString*)javaScriptToDispatchPopStateWithObject:(NSString*)stateObjectJSON; |
| // Generates the JavaScript string used to manually dispatch a hashchange event, |
| // using |oldURL| and |newURL| as the event parameters. |
| - (NSString*)javaScriptToDispatchHashChangeWithOldURL:(const GURL&)oldURL |
| newURL:(const GURL&)newURL; |
| // Injects JavaScript to update the URL and state object of the webview to the |
| // values found in the current NavigationItem. A hashchange event will be |
| // dispatched if |dispatchHashChange| is YES, and a popstate event will be |
| // dispatched if |sameDocument| is YES. Upon the script's completion, resets |
| // |urlOnStartLoading_| and |_lastRegisteredRequestURL| to the current |
| // NavigationItem's URL. This is necessary so that sites that depend on URL |
| // params/fragments continue to work correctly and that checks for the URL don't |
| // incorrectly trigger |-webPageChangedWithContext| calls. |
| - (void)injectHTML5HistoryScriptWithHashChange:(BOOL)dispatchHashChange |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation; |
| |
| // WKNavigation objects are used as a weak key to store web::NavigationContext. |
| // WKWebView manages WKNavigation lifetime and destroys them after the |
| // navigation is finished. However for window opening navigations WKWebView |
| // passes null WKNavigation to WKNavigationDelegate callbacks and strong key is |
| // used to store web::NavigationContext. Those "null" navigations have to be |
| // cleaned up manually by calling this method. |
| - (void)forgetNullWKNavigation:(WKNavigation*)navigation; |
| |
| // Returns YES if the current live view is a web view with an image MIME type. |
| - (BOOL)contentIsImage; |
| // Restores the state for this page from session history. |
| - (void)restoreStateFromHistory; |
| // Extracts the current page's viewport tag information and calls |completion|. |
| // If the page has changed before the viewport tag is successfully extracted, |
| // |completion| is called with nullptr. |
| typedef void (^ViewportStateCompletion)(const web::PageViewportState*); |
| - (void)extractViewportTagWithCompletion:(ViewportStateCompletion)completion; |
| // Called by NSNotificationCenter upon orientation changes. |
| - (void)orientationDidChange; |
| // Queries the web view for the user-scalable meta tag and calls |
| // |-applyPageDisplayState:userScalable:| with the result. |
| - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState; |
| // Restores state of the web view's scroll view from |scrollState|. |
| // |isUserScalable| represents the value of user-scalable meta tag. |
| - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState |
| userScalable:(BOOL)isUserScalable; |
| // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view. |
| // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|. |
| - (void)prepareToApplyWebViewScrollZoomScale; |
| // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view. |
| // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|. |
| - (void)finishApplyingWebViewScrollZoomScale; |
| // Sets zoom scale value for webview scroll view from |zoomState|. |
| - (void)applyWebViewScrollZoomScaleFromZoomState: |
| (const web::PageZoomState&)zoomState; |
| // Sets scroll offset value for webview scroll view from |scrollState|. |
| - (void)applyWebViewScrollOffsetFromScrollState: |
| (const web::PageScrollState&)scrollState; |
| // Returns the referrer for the current page. |
| - (web::Referrer)currentReferrer; |
| // Adds a new NavigationItem with the given URL and state object to the history |
| // stack. A state object is a serialized generic JavaScript object that contains |
| // details of the UI's state for a given NavigationItem/URL. |
| // TODO(stuartmorgan): Move the pushState/replaceState logic into |
| // NavigationManager. |
| - (void)pushStateWithPageURL:(const GURL&)pageURL |
| stateObject:(NSString*)stateObject |
| transition:(ui::PageTransition)transition |
| hasUserGesture:(BOOL)hasUserGesture; |
| // Assigns the given URL and state object to the current NavigationItem. |
| - (void)replaceStateWithPageURL:(const GURL&)pageUrl |
| stateObject:(NSString*)stateObject |
| hasUserGesture:(BOOL)hasUserGesture; |
| // Sets _documentURL to newURL, and updates any relevant state information. |
| - (void)setDocumentURL:(const GURL&)newURL |
| context:(web::NavigationContextImpl*)context; |
| // Sets last committed NavigationItem's title to the given |title|, which can |
| // not be nil. |
| - (void)setNavigationItemTitle:(NSString*)title; |
| // Returns YES if the current navigation item corresponds to a web page |
| // loaded by a POST request. |
| - (BOOL)isCurrentNavigationItemPOST; |
| // Returns YES if current navigation item is WKNavigationTypeBackForward. |
| - (BOOL)isCurrentNavigationBackForward; |
| // Returns YES if the given WKBackForwardListItem is valid to use for |
| // navigation. |
| - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item; |
| // Finds all the scrollviews in the view hierarchy and makes sure they do not |
| // interfere with scroll to top when tapping the statusbar. |
| - (void)optOutScrollsToTopForSubviews; |
| // Returns YES if the navigation action is associated with a main frame request. |
| - (BOOL)isMainFrameNavigationAction:(WKNavigationAction*)action; |
| // Updates SSL status for the current navigation item based on the information |
| // provided by web view. |
| - (void)updateSSLStatusForCurrentNavigationItem; |
| // Called when a load ends in an SSL error and certificate chain. |
| - (void)handleSSLCertError:(NSError*)error |
| forNavigation:(WKNavigation*)navigation; |
| |
| // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to |
| // reply with NSURLSessionAuthChallengeDisposition and credentials. |
| - (void)processAuthChallenge:(NSURLAuthenticationChallenge*)challenge |
| forCertAcceptPolicy:(web::CertAcceptPolicy)policy |
| certStatus:(net::CertStatus)certStatus |
| completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, |
| NSURLCredential*))completionHandler; |
| // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to reply |
| // with NSURLSessionAuthChallengeDisposition and credentials. |
| - (void)handleHTTPAuthForChallenge:(NSURLAuthenticationChallenge*)challenge |
| completionHandler: |
| (void (^)(NSURLSessionAuthChallengeDisposition, |
| NSURLCredential*))completionHandler; |
| // Used in webView:didReceiveAuthenticationChallenge:completionHandler: to reply |
| // with NSURLSessionAuthChallengeDisposition and credentials. |
| + (void)processHTTPAuthForUser:(NSString*)user |
| password:(NSString*)password |
| completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, |
| NSURLCredential*))completionHandler; |
| |
| // Helper to respond to |webView:runJavaScript...| delegate methods. |
| // |completionHandler| must not be nil. |
| - (void)runJavaScriptDialogOfType:(web::JavaScriptDialogType)type |
| initiatedByFrame:(WKFrameInfo*)frame |
| message:(NSString*)message |
| defaultText:(NSString*)defaultText |
| completion:(void (^)(BOOL, NSString*))completionHandler; |
| |
| // Called when WKWebView estimatedProgress has been changed. |
| - (void)webViewEstimatedProgressDidChange; |
| // Called when WKWebView certificateChain or hasOnlySecureContent property has |
| // changed. |
| - (void)webViewSecurityFeaturesDidChange; |
| // Called when WKWebView loading state has been changed. |
| - (void)webViewLoadingStateDidChange; |
| // Called when WKWebView title has been changed. |
| - (void)webViewTitleDidChange; |
| // Called when WKWebView canGoForward/canGoBack state has been changed. |
| - (void)webViewBackForwardStateDidChange; |
| // Called when WKWebView URL has been changed. |
| - (void)webViewURLDidChange; |
| // Returns YES if a KVO change to |newURL| could be a 'navigation' within the |
| // document (hash change, pushState/replaceState, etc.). This should only be |
| // used in the context of a URL KVO callback firing, and only if |isLoading| is |
| // YES for the web view (since if it's not, no guesswork is needed). |
| - (BOOL)isKVOChangePotentialSameDocumentNavigationToURL:(const GURL&)newURL; |
| // Returns YES if a SafeBrowsing warning is currently displayed within |
| // WKWebView. |
| - (BOOL)isSafeBrowsingWarningDisplayedInWebView; |
| // Called when a non-document-changing URL change occurs. Updates the |
| // _documentURL, and informs the superclass of the change. |
| - (void)URLDidChangeWithoutDocumentChange:(const GURL&)URL; |
| // Returns context for pending navigation that has |URL|. null if there is no |
| // matching pending navigation. |
| - (web::NavigationContextImpl*)contextForPendingMainFrameNavigationWithURL: |
| (const GURL&)URL; |
| // Loads request for the URL of the current navigation item. Subclasses may |
| // choose to build a new NSURLRequest and call |loadRequest| on the underlying |
| // web view, or use native web view navigation where possible (for example, |
| // going back and forward through the history stack). |
| - (void)loadRequestForCurrentNavigationItem; |
| // Reports Navigation.IOSWKWebViewSlowFastBackForward UMA. No-op if pending |
| // navigation is not back forward navigation. |
| - (void)reportBackForwardNavigationTypeForFastNavigation:(BOOL)isFast; |
| |
| // Handlers for JavaScript messages. |message| contains a JavaScript command and |
| // data relevant to the message, and |context| contains contextual information |
| // about web view state needed for some handlers. |
| |
| // Handles 'chrome.send' message. |
| - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'document.favicons' message. |
| - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.error' message. |
| - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.hashchange' message. |
| - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.history.back' message. |
| - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.history.forward' message. |
| - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.history.go' message. |
| - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.history.willChangeState' message. |
| - (BOOL)handleWindowHistoryWillChangeStateMessage: |
| (base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| // Handles 'window.history.didPushState' message. |
| - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| |
| // Handles 'window.history.didReplaceState' message. |
| - (BOOL)handleWindowHistoryDidReplaceStateMessage: |
| (base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| |
| // Handles 'restoresession.error' message. |
| - (BOOL)handleRestoreSessionErrorMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context; |
| |
| // Caches request POST data in the given session entry. |
| - (void)cachePOSTDataForRequest:(NSURLRequest*)request |
| inNavigationItem:(web::NavigationItemImpl*)item; |
| |
| // Returns YES if the given |action| should be allowed to continue for app |
| // specific URL. If this returns NO, the navigation should be cancelled. |
| // App specific pages have elevated privileges and WKWebView uses the same |
| // renderer process for all page frames. With that Chromium does not allow |
| // running App specific pages in the same process as a web site from the |
| // internet. Allows navigation to app specific URL in the following cases: |
| // - last committed URL is app specific |
| // - navigation not a new navigation (back-forward or reload) |
| // - navigation is typed, generated or bookmark |
| // - navigation is performed in iframe and main frame is app-specific page |
| - (BOOL)shouldAllowAppSpecificURLNavigationAction:(WKNavigationAction*)action |
| transition: |
| (ui::PageTransition)pageTransition; |
| // Called when a load ends in an error. |
| - (void)handleLoadError:(NSError*)error |
| forNavigation:(WKNavigation*)navigation |
| provisionalLoad:(BOOL)provisionalLoad; |
| |
| // Handles cancelled load in WKWebView (error with NSURLErrorCancelled code). |
| - (void)handleCancelledError:(NSError*)error |
| forNavigation:(WKNavigation*)navigation |
| provisionalLoad:(BOOL)provisionalLoad; |
| |
| // Used to decide whether a load that generates errors with the |
| // NSURLErrorCancelled code should be cancelled. |
| - (BOOL)shouldCancelLoadForCancelledError:(NSError*)error |
| provisionalLoad:(BOOL)provisionalLoad; |
| |
| // Returns YES if response should be rendered in WKWebView. |
| - (BOOL)shouldRenderResponse:(WKNavigationResponse*)WKResponse; |
| |
| // Creates DownloadTask for the given navigation response. Headers are passed |
| // as argument to avoid extra NSDictionary -> net::HttpResponseHeaders |
| // conversion. |
| - (void)createDownloadTaskForResponse:(WKNavigationResponse*)WKResponse |
| HTTPHeaders:(net::HttpResponseHeaders*)headers; |
| |
| // This method should be called on receiving WKNavigationDelegate callbacks. It |
| // will log a metric if the callback occurs after the reciever has already been |
| // closed. It also stops the SafeBrowsing warning detection timer, since after |
| // this point it's too late for a SafeBrowsing warning to be displayed for the |
| // navigation for which the timer was started. |
| - (void)didReceiveWebViewNavigationDelegateCallback; |
| |
| // Sets up WebUI for URL. |
| - (void)createWebUIForURL:(const GURL&)URL; |
| // Clears WebUI, if one exists. |
| - (void)clearWebUI; |
| |
| @end |
| |
| namespace { |
| |
| NSString* const kReferrerHeaderName = @"Referer"; // [sic] |
| |
| // The duration of the period following a screen touch during which the user is |
| // still considered to be interacting with the page. |
| const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2; |
| |
| // URLs that are fed into UIWebView as history push/replace get escaped, |
| // potentially changing their format. Code that attempts to determine whether a |
| // URL hasn't changed can be confused by those differences though, so method |
| // will round-trip a URL through the escaping process so that it can be adjusted |
| // pre-storing, to allow later comparisons to work as expected. |
| GURL URLEscapedForHistory(const GURL& url) { |
| // TODO(stuartmorgan): This is a very large hammer; see if limited unicode |
| // escaping would be sufficient. |
| return net::GURLWithNSURL(net::NSURLWithGURL(url)); |
| } |
| } // namespace |
| |
| @implementation CRWWebController |
| |
| @synthesize webUsageEnabled = _webUsageEnabled; |
| @synthesize loadPhase = _loadPhase; |
| @synthesize webProcessCrashed = _webProcessCrashed; |
| @synthesize visible = _visible; |
| @synthesize nativeProvider = _nativeProvider; |
| @synthesize swipeRecognizerProvider = _swipeRecognizerProvider; |
| @synthesize webViewProxy = _webViewProxy; |
| @synthesize allowsBackForwardNavigationGestures = |
| _allowsBackForwardNavigationGestures; |
| |
| - (instancetype)initWithWebState:(WebStateImpl*)webState { |
| self = [super init]; |
| if (self) { |
| _webStateImpl = webState; |
| _webUsageEnabled = YES; |
| |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) |
| _allowsBackForwardNavigationGestures = YES; |
| |
| DCHECK(_webStateImpl); |
| // Load phase when no WebView present is 'loaded' because this represents |
| // the idle state. |
| _loadPhase = web::PAGE_LOADED; |
| // Content area is lazily instantiated. |
| _defaultURL = GURL(url::kAboutBlankURL); |
| _jsInjectionReceiver = |
| [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]; |
| _webViewProxy = [[CRWWebViewProxyImpl alloc] initWithWebController:self]; |
| [[_webViewProxy scrollViewProxy] addObserver:self]; |
| _gestureRecognizers = [[NSMutableArray alloc] init]; |
| _pendingLoadCompleteActions = [[NSMutableArray alloc] init]; |
| web::BrowserState* browserState = _webStateImpl->GetBrowserState(); |
| _certVerificationController = [[CRWCertVerificationController alloc] |
| initWithBrowserState:browserState]; |
| _certVerificationErrors = |
| std::make_unique<CertVerificationErrorsCacheType>(kMaxCertErrorsCount); |
| _navigationStates = [[CRWWKNavigationStates alloc] init]; |
| web::WebFramesManagerImpl::CreateForWebState(_webStateImpl); |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(orientationDidChange) |
| name:UIApplicationDidChangeStatusBarOrientationNotification |
| object:nil]; |
| } |
| return self; |
| } |
| |
| - (WebState*)webState { |
| return _webStateImpl; |
| } |
| |
| - (WebStateImpl*)webStateImpl { |
| return _webStateImpl; |
| } |
| |
| - (void)clearTransientContentView { |
| // Early return if there is no transient content view. |
| if (![_containerView transientContentView]) |
| return; |
| |
| // Remove the transient content view from the hierarchy. |
| [_containerView clearTransientContentView]; |
| } |
| |
| - (void)showTransientContentView:(CRWContentView*)contentView { |
| DCHECK(contentView); |
| DCHECK(contentView.scrollView); |
| // TODO(crbug.com/556848) Reenable DCHECK when |CRWWebControllerContainerView| |
| // is restructured so that subviews are not added during |layoutSubviews|. |
| // DCHECK([contentView.scrollView isDescendantOfView:contentView]); |
| [_containerView displayTransientContent:contentView]; |
| } |
| |
| - (void)dealloc { |
| DCHECK([NSThread isMainThread]); |
| DCHECK(_isBeingDestroyed); // 'close' must have been called already. |
| DCHECK(!_webView); |
| } |
| |
| - (id<CRWNativeContent>)nativeController { |
| return [_containerView nativeController]; |
| } |
| |
| - (void)setNativeController:(id<CRWNativeContent>)nativeController { |
| // Check for pointer equality. |
| if (self.nativeController == nativeController) |
| return; |
| |
| // Unset the delegate on the previous instance. |
| if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) |
| [self.nativeController setDelegate:nil]; |
| |
| [_containerView displayNativeContent:nativeController]; |
| [self setNativeControllerWebUsageEnabled:_webUsageEnabled]; |
| } |
| |
| - (NSDictionary*)WKWebViewObservers { |
| return @{ |
| @"serverTrust" : @"webViewSecurityFeaturesDidChange", |
| @"estimatedProgress" : @"webViewEstimatedProgressDidChange", |
| @"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange", |
| @"title" : @"webViewTitleDidChange", |
| @"loading" : @"webViewLoadingStateDidChange", |
| @"URL" : @"webViewURLDidChange", |
| @"canGoForward" : @"webViewBackForwardStateDidChange", |
| @"canGoBack" : @"webViewBackForwardStateDidChange" |
| }; |
| } |
| |
| // NativeControllerDelegate method, called to inform that title has changed. |
| - (void)nativeContent:(id)content titleDidChange:(NSString*)title { |
| [self setNavigationItemTitle:title]; |
| } |
| |
| - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled { |
| if ([self.nativeController |
| respondsToSelector:@selector(setWebUsageEnabled:)]) { |
| [self.nativeController setWebUsageEnabled:webUsageEnabled]; |
| } |
| } |
| |
| - (void)setWebUsageEnabled:(BOOL)enabled { |
| if (_webUsageEnabled == enabled) |
| return; |
| // WKWebView autoreleases its WKProcessPool on removal from superview. |
| // Deferring WKProcessPool deallocation may lead to issues with cookie |
| // clearing and and Browsing Data Partitioning implementation. |
| @autoreleasepool { |
| if (!enabled) { |
| [self removeWebView]; |
| } |
| } |
| |
| _webUsageEnabled = enabled; |
| |
| // WKWebView autoreleases its WKProcessPool on removal from superview. |
| // Deferring WKProcessPool deallocation may lead to issues with cookie |
| // clearing and and Browsing Data Partitioning implementation. |
| @autoreleasepool { |
| [self setNativeControllerWebUsageEnabled:_webUsageEnabled]; |
| if (enabled) { |
| // Don't create the web view; let it be lazy created as needed. |
| } else { |
| _webStateImpl->ClearTransientContent(); |
| _touchTrackingRecognizer.touchTrackingDelegate = nil; |
| _touchTrackingRecognizer = nil; |
| _currentURLLoadWasTrigerred = NO; |
| } |
| } |
| } |
| |
| - (void)requirePageReconstruction { |
| // TODO(crbug.com/736103): Removing web view will destroy session history for |
| // WKBasedNavigationManager. |
| if (!web::GetWebClient()->IsSlimNavigationManagerEnabled()) |
| [self removeWebView]; |
| } |
| |
| - (BOOL)isViewAlive { |
| return !_webProcessCrashed && [_containerView isViewAlive]; |
| } |
| |
| - (BOOL)isSafeBrowsingWarningDisplayedInWebView { |
| // A SafeBrowsing warning is a UIScrollView that is inserted on top of |
| // WKWebView's scroll view. This method uses heuristics to detect this view. |
| // It may break in the future if WebKit's implementation of SafeBrowsing |
| // warnings changes. |
| UIView* containingView = _webView.scrollView.superview; |
| if (!containingView) |
| return NO; |
| |
| UIView* topView = containingView.subviews.lastObject; |
| |
| if (topView == _webView.scrollView) |
| return NO; |
| |
| return |
| [topView isKindOfClass:[UIScrollView class]] && |
| [NSStringFromClass([topView class]) containsString:@"Warning"] && |
| topView.subviews.count > 0 && |
| [topView.subviews[0].subviews.lastObject isKindOfClass:[UIButton class]]; |
| } |
| |
| - (BOOL)contentIsHTML { |
| if (!_webView) |
| return NO; |
| |
| std::string MIMEType = self.webState->GetContentsMimeType(); |
| return MIMEType == "text/html" || MIMEType == "application/xhtml+xml" || |
| MIMEType == "application/xml"; |
| } |
| |
| // Stop doing stuff, especially network stuff. Close the request tracker. |
| - (void)terminateNetworkActivity { |
| DCHECK(!_isHalted); |
| _isHalted = YES; |
| |
| // Cancel all outstanding perform requests, and clear anything already queued |
| // (since this may be called from within the handling loop) to prevent any |
| // asynchronous JavaScript invocation handling from continuing. |
| [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| } |
| |
| - (void)dismissModals { |
| if ([self.nativeController respondsToSelector:@selector(dismissModals)]) |
| [self.nativeController dismissModals]; |
| } |
| |
| // Caller must reset the delegate before calling. |
| - (void)close { |
| _webStateImpl->CancelDialogs(); |
| |
| _SSLStatusUpdater = nil; |
| |
| self.nativeProvider = nil; |
| self.swipeRecognizerProvider = nil; |
| if ([self.nativeController respondsToSelector:@selector(close)]) |
| [self.nativeController close]; |
| |
| if (!_isHalted) { |
| [self terminateNetworkActivity]; |
| } |
| |
| // Mark the destruction sequence has started, in case someone else holds a |
| // strong reference and tries to continue using the tab. |
| DCHECK(!_isBeingDestroyed); |
| _isBeingDestroyed = YES; |
| |
| // Remove the web view now. Otherwise, delegate callbacks occur. |
| [self removeWebView]; |
| |
| // Explicitly reset content to clean up views and avoid dangling KVO |
| // observers. |
| [_containerView resetContent]; |
| |
| _webStateImpl = nullptr; |
| |
| DCHECK(!_webView); |
| // TODO(crbug.com/662860): Don't set the delegate to nil. |
| [_containerView setDelegate:nil]; |
| if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) { |
| [self.nativeController setDelegate:nil]; |
| } |
| _touchTrackingRecognizer.touchTrackingDelegate = nil; |
| [[_webViewProxy scrollViewProxy] removeObserver:self]; |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| } |
| |
| - (CGPoint)scrollPosition { |
| return self.webScrollView.contentOffset; |
| } |
| |
| - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel { |
| DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory"; |
| |
| // The web view URL is the current URL only if it is neither a placeholder URL |
| // (used to hold WKBackForwardListItem for WebUI and Native Content views) nor |
| // a restore_session.html (used to replay session history in WKWebView). |
| // TODO(crbug.com/738020): Investigate if this method is still needed and if |
| // it can be implemented using NavigationManager API after removal of legacy |
| // navigation stack. |
| GURL webViewURL = net::GURLWithNSURL(_webView.URL); |
| if (_webView && !IsWKInternalUrl(webViewURL)) { |
| return [self webURLWithTrustLevel:trustLevel]; |
| } |
| // Any non-web URL source is trusted. |
| *trustLevel = web::URLVerificationTrustLevel::kAbsolute; |
| if (self.nativeController) { |
| if ([self.nativeController respondsToSelector:@selector(virtualURL)]) { |
| return [self.nativeController virtualURL]; |
| } else { |
| return [self.nativeController url]; |
| } |
| } |
| web::NavigationItem* item = |
| self.navigationManagerImpl |
| ->GetLastCommittedItemInCurrentOrRestoredSession(); |
| return item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| } |
| |
| - (WKWebView*)webView { |
| return _webView; |
| } |
| |
| - (UIScrollView*)webScrollView { |
| return [_webView scrollView]; |
| } |
| |
| - (GURL)currentURL { |
| web::URLVerificationTrustLevel trustLevel = |
| web::URLVerificationTrustLevel::kNone; |
| return [self currentURLWithTrustLevel:&trustLevel]; |
| } |
| |
| - (web::Referrer)currentReferrer { |
| // Referrer string doesn't include the fragment, so in cases where the |
| // previous URL is equal to the current referrer plus the fragment the |
| // previous URL is returned as current referrer. |
| NSString* referrerString = _currentReferrerString; |
| |
| // In case of an error evaluating the JavaScript simply return empty string. |
| if ([referrerString length] == 0) |
| return web::Referrer(); |
| |
| web::NavigationItem* item = self.currentNavItem; |
| GURL navigationURL = item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| NSString* previousURLString = base::SysUTF8ToNSString(navigationURL.spec()); |
| // Check if the referrer is equal to the previous URL minus the hash symbol. |
| // L'#' is used to convert the char '#' to a unichar. |
| if ([previousURLString length] > [referrerString length] && |
| [previousURLString hasPrefix:referrerString] && |
| [previousURLString characterAtIndex:[referrerString length]] == L'#') { |
| referrerString = previousURLString; |
| } |
| // Since referrer is being extracted from the destination page, the correct |
| // policy from the origin has *already* been applied. Since the extracted URL |
| // is the post-policy value, and the source policy is no longer available, |
| // the policy is set to Always so that whatever WebKit decided to send will be |
| // re-sent when replaying the entry. |
| // TODO(stuartmorgan): When possible, get the real referrer and policy in |
| // advance and use that instead. https://crbug.com/227769. |
| return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)), |
| web::ReferrerPolicyAlways); |
| } |
| |
| - (void)pushStateWithPageURL:(const GURL&)pageURL |
| stateObject:(NSString*)stateObject |
| transition:(ui::PageTransition)transition |
| hasUserGesture:(BOOL)hasUserGesture { |
| std::unique_ptr<web::NavigationContextImpl> context = |
| web::NavigationContextImpl::CreateNavigationContext( |
| _webStateImpl, pageURL, hasUserGesture, transition, |
| /*is_renderer_initiated=*/true); |
| context->SetIsSameDocument(true); |
| _webStateImpl->OnNavigationStarted(context.get()); |
| self.navigationManagerImpl->AddPushStateItemIfNecessary(pageURL, stateObject, |
| transition); |
| context->SetHasCommitted(true); |
| _webStateImpl->OnNavigationFinished(context.get()); |
| self.userInteractionRegistered = NO; |
| } |
| |
| - (void)replaceStateWithPageURL:(const GURL&)pageURL |
| stateObject:(NSString*)stateObject |
| hasUserGesture:(BOOL)hasUserGesture { |
| std::unique_ptr<web::NavigationContextImpl> context = |
| web::NavigationContextImpl::CreateNavigationContext( |
| _webStateImpl, pageURL, hasUserGesture, |
| ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, |
| /*is_renderer_initiated=*/true); |
| context->SetIsSameDocument(true); |
| _webStateImpl->OnNavigationStarted(context.get()); |
| self.navigationManagerImpl->UpdateCurrentItemForReplaceState(pageURL, |
| stateObject); |
| context->SetHasCommitted(true); |
| _webStateImpl->OnNavigationFinished(context.get()); |
| } |
| |
| - (void)setDocumentURL:(const GURL&)newURL |
| context:(web::NavigationContextImpl*)context { |
| if (newURL != _documentURL && newURL.is_valid()) { |
| _documentURL = newURL; |
| _interactionRegisteredSinceLastURLChange = NO; |
| } |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && context && |
| !context->IsLoadingHtmlString() && !context->IsLoadingErrorPage() && |
| !IsWKInternalUrl(newURL) && !newURL.SchemeIs(url::kAboutScheme) && |
| _webView) { |
| GURL documentOrigin = newURL.GetOrigin(); |
| web::NavigationItem* committedItem = |
| _webStateImpl->GetNavigationManager()->GetLastCommittedItem(); |
| GURL committedOrigin = |
| committedItem ? committedItem->GetURL().GetOrigin() : GURL::EmptyGURL(); |
| DCHECK_EQ(documentOrigin, committedOrigin) |
| << "Old and new URL detection system have a mismatch"; |
| |
| ukm::SourceId sourceID = ukm::ConvertToSourceId( |
| context->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID); |
| if (sourceID != ukm::kInvalidSourceId) { |
| ukm::builders::IOS_URLMismatchInLegacyAndSlimNavigationManager(sourceID) |
| .SetHasMismatch(documentOrigin != committedOrigin) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| } |
| } |
| |
| - (void)setNavigationItemTitle:(NSString*)title { |
| DCHECK(title); |
| web::NavigationItem* item = |
| self.navigationManagerImpl->GetLastCommittedItem(); |
| if (!item) |
| return; |
| |
| item->SetTitle(base::SysNSStringToUTF16(title)); |
| _webStateImpl->OnTitleChanged(); |
| } |
| |
| - (BOOL)isCurrentNavigationItemPOST { |
| // |_pendingNavigationInfo| will be nil if the decidePolicy* delegate methods |
| // were not called. |
| NSString* HTTPMethod = |
| _pendingNavigationInfo |
| ? [_pendingNavigationInfo HTTPMethod] |
| : [self currentBackForwardListItemHolder]->http_method(); |
| if ([HTTPMethod isEqual:@"POST"]) { |
| return YES; |
| } |
| if (!self.currentNavItem) { |
| return NO; |
| } |
| return self.currentNavItem->HasPostData(); |
| } |
| |
| - (BOOL)isCurrentNavigationBackForward { |
| if (!self.currentNavItem) |
| return NO; |
| WKNavigationType currentNavigationType = |
| [self currentBackForwardListItemHolder]->navigation_type(); |
| return currentNavigationType == WKNavigationTypeBackForward; |
| } |
| |
| - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item { |
| // The current back-forward list item MUST be in the WKWebView's back-forward |
| // list to be valid. |
| WKBackForwardList* list = [_webView backForwardList]; |
| return list.currentItem == item || |
| [list.forwardList indexOfObject:item] != NSNotFound || |
| [list.backList indexOfObject:item] != NSNotFound; |
| } |
| |
| - (UIView*)view { |
| [self ensureContainerViewCreated]; |
| DCHECK(_containerView); |
| return _containerView; |
| } |
| |
| - (id<CRWWebViewNavigationProxy>)webViewNavigationProxy { |
| return static_cast<id<CRWWebViewNavigationProxy>>(self.webView); |
| } |
| |
| - (UIView*)viewForPrinting { |
| // Printing is not supported for native controllers. |
| return _webView; |
| } |
| |
| - (double)loadingProgress { |
| return [_webView estimatedProgress]; |
| } |
| |
| - (std::unique_ptr<web::NavigationContextImpl>) |
| registerLoadRequestForURL:(const GURL&)URL |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation |
| hasUserGesture:(BOOL)hasUserGesture |
| placeholderNavigation:(BOOL)placeholderNavigation { |
| // Get the navigation type from the last main frame load request, and try to |
| // map that to a PageTransition. |
| WKNavigationType navigationType = |
| _pendingNavigationInfo ? [_pendingNavigationInfo navigationType] |
| : WKNavigationTypeOther; |
| ui::PageTransition transition = |
| [self pageTransitionFromNavigationType:navigationType]; |
| |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && |
| navigationType == WKNavigationTypeBackForward && |
| _webView.backForwardList.currentItem) { |
| web::NavigationItem* currentItem = [[CRWNavigationItemHolder |
| holderForBackForwardListItem:_webView.backForwardList.currentItem] |
| navigationItem]; |
| if (currentItem) { |
| transition = ui::PageTransitionFromInt(transition | |
| currentItem->GetTransitionType()); |
| } |
| } |
| |
| // The referrer is not known yet, and will be updated later. |
| const web::Referrer emptyReferrer; |
| std::unique_ptr<web::NavigationContextImpl> context = |
| [self registerLoadRequestForURL:URL |
| referrer:emptyReferrer |
| transition:transition |
| sameDocumentNavigation:sameDocumentNavigation |
| hasUserGesture:hasUserGesture |
| placeholderNavigation:placeholderNavigation]; |
| context->SetWKNavigationType(navigationType); |
| return context; |
| } |
| |
| - (std::unique_ptr<web::NavigationContextImpl>) |
| registerLoadRequestForURL:(const GURL&)requestURL |
| referrer:(const web::Referrer&)referrer |
| transition:(ui::PageTransition)transition |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation |
| hasUserGesture:(BOOL)hasUserGesture |
| placeholderNavigation:(BOOL)placeholderNavigation { |
| // Transfer time is registered so that further transitions within the time |
| // envelope are not also registered as links. |
| _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent(); |
| |
| // Add or update pending item before any WebStateObserver callbacks. |
| // See https://crbug.com/842151 for a scenario where this is important. |
| web::NavigationItem* item = |
| self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession(); |
| if (item) { |
| // Update the existing pending entry. |
| // Typically on PAGE_TRANSITION_CLIENT_REDIRECT. |
| // Don't update if request is a placeholder entry because the pending item |
| // should have the original target URL. |
| // Don't update if pending URL has a different origin, because client |
| // redirects can not change the origin. It is possible to have more than one |
| // pending navigations, so the redirect does not necesserily belong to the |
| // pending navigation item. |
| if (!placeholderNavigation && |
| item->GetURL().GetOrigin() == requestURL.GetOrigin()) { |
| self.navigationManagerImpl->UpdatePendingItemUrl(requestURL); |
| } |
| } else { |
| self.navigationManagerImpl->AddPendingItem( |
| requestURL, referrer, transition, |
| web::NavigationInitiationType::RENDERER_INITIATED, |
| NavigationManager::UserAgentOverrideOption::INHERIT); |
| item = |
| self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession(); |
| } |
| |
| bool redirect = transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK; |
| if (!redirect) { |
| // Before changing phases, the delegate should be informed that any existing |
| // request is being cancelled before completion. |
| [self loadCancelled]; |
| DCHECK(_loadPhase == web::PAGE_LOADED); |
| } |
| |
| _loadPhase = web::LOAD_REQUESTED; |
| |
| // Record the state of outgoing web view. Do nothing if native controller |
| // exists, because in that case recordStateInHistory will record the state |
| // of incoming page as native controller is already inserted. |
| // TODO(crbug.com/811770) Don't record state under WKBasedNavigationManager |
| // because it may incorrectly clobber the incoming page if this is a |
| // back/forward navigation. WKWebView restores page scroll state for web view |
| // pages anyways so this only impacts user if WKWebView is deleted. |
| if (!redirect && !self.nativeController && |
| !web::GetWebClient()->IsSlimNavigationManagerEnabled()) { |
| [self recordStateInHistory]; |
| } |
| |
| bool isRendererInitiated = |
| item ? (static_cast<web::NavigationItemImpl*>(item) |
| ->NavigationInitiationType() == |
| web::NavigationInitiationType::RENDERER_INITIATED) |
| : true; |
| std::unique_ptr<web::NavigationContextImpl> context = |
| web::NavigationContextImpl::CreateNavigationContext( |
| _webStateImpl, requestURL, hasUserGesture, transition, |
| isRendererInitiated); |
| context->SetPlaceholderNavigation(placeholderNavigation); |
| |
| // TODO(crbug.com/676129): LegacyNavigationManagerImpl::AddPendingItem does |
| // not create a pending item in case of reload. Remove this workaround once |
| // the bug is fixed or WKBasedNavigationManager is fully adopted. |
| if (!item) { |
| DCHECK(!web::GetWebClient()->IsSlimNavigationManagerEnabled()); |
| item = self.navigationManagerImpl->GetLastCommittedItem(); |
| } |
| |
| context->SetNavigationItemUniqueID(item->GetUniqueID()); |
| context->SetIsPost([self isCurrentNavigationItemPOST]); |
| context->SetIsSameDocument(sameDocumentNavigation); |
| |
| if (!IsWKInternalUrl(requestURL) && !placeholderNavigation) { |
| _webStateImpl->SetIsLoading(true); |
| |
| // WKBasedNavigationManager triggers HTML load when placeholder navigation |
| // finishes. |
| if (!web::GetWebClient()->IsSlimNavigationManagerEnabled()) |
| [_webUIManager loadWebUIForURL:requestURL]; |
| } |
| return context; |
| } |
| |
| - (ui::PageTransition)pageTransitionFromNavigationType: |
| (WKNavigationType)navigationType { |
| switch (navigationType) { |
| case WKNavigationTypeLinkActivated: |
| return ui::PAGE_TRANSITION_LINK; |
| case WKNavigationTypeFormSubmitted: |
| case WKNavigationTypeFormResubmitted: |
| return ui::PAGE_TRANSITION_FORM_SUBMIT; |
| case WKNavigationTypeBackForward: |
| return ui::PAGE_TRANSITION_FORWARD_BACK; |
| case WKNavigationTypeReload: |
| return ui::PAGE_TRANSITION_RELOAD; |
| case WKNavigationTypeOther: |
| // The "Other" type covers a variety of very different cases, which may |
| // or may not be the result of user actions. For now, guess based on |
| // whether there's been an interaction since the last URL change. |
| // TODO(crbug.com/549301): See if this heuristic can be improved. |
| return _interactionRegisteredSinceLastURLChange |
| ? ui::PAGE_TRANSITION_LINK |
| : ui::PAGE_TRANSITION_CLIENT_REDIRECT; |
| } |
| } |
| |
| // TODO(crbug.com/788465): Verify that the history state management here are not |
| // needed for WKBasedNavigationManagerImpl and delete this method. The |
| // OnNavigationItemCommitted() call is likely the only thing that needs to be |
| // retained. |
| - (void)updateHTML5HistoryState { |
| web::NavigationItemImpl* currentItem = self.currentNavItem; |
| if (!currentItem) |
| return; |
| |
| // Same-document navigations must trigger a popState event. |
| CRWSessionController* sessionController = self.sessionController; |
| BOOL sameDocumentNavigation = [sessionController |
| isSameDocumentNavigationBetweenItem:sessionController.currentItem |
| andItem:sessionController.previousItem]; |
| // WKWebView doesn't send hashchange events for same-document non-BFLI |
| // navigations, so one must be dispatched manually for hash change same- |
| // document navigations. |
| const GURL URL = currentItem->GetURL(); |
| web::NavigationItem* previousItem = self.sessionController.previousItem; |
| const GURL oldURL = previousItem ? previousItem->GetURL() : GURL(); |
| BOOL shouldDispatchHashchange = sameDocumentNavigation && previousItem && |
| (web::GURLByRemovingRefFromGURL(URL) == |
| web::GURLByRemovingRefFromGURL(oldURL)); |
| // The URL and state object must be set for same-document navigations and |
| // NavigationItems that were created or updated by calls to pushState() or |
| // replaceState(). |
| BOOL shouldUpdateState = sameDocumentNavigation || |
| currentItem->IsCreatedFromPushState() || |
| currentItem->HasStateBeenReplaced(); |
| if (!shouldUpdateState) |
| return; |
| |
| // TODO(stuartmorgan): Make CRWSessionController manage this internally (or |
| // remove it; it's not clear this matches other platforms' behavior). |
| self.navigationManagerImpl->OnNavigationItemCommitted(); |
| // Record that a same-document hashchange event will be fired. This flag will |
| // be reset when resonding to the hashchange message. Note that resetting the |
| // flag in the completion block below is too early, as that block is called |
| // before hashchange event listeners have a chance to fire. |
| _dispatchingSameDocumentHashChangeEvent = shouldDispatchHashchange; |
| // Inject the JavaScript to update the state on the browser side. |
| [self injectHTML5HistoryScriptWithHashChange:shouldDispatchHashchange |
| sameDocumentNavigation:sameDocumentNavigation]; |
| } |
| |
| - (NSString*)javaScriptToReplaceWebViewURL:(const GURL&)URL |
| stateObjectJSON:(NSString*)stateObject { |
| std::string outURL; |
| base::EscapeJSONString(URL.spec(), true, &outURL); |
| return |
| [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);", |
| base::SysUTF8ToNSString(outURL), stateObject]; |
| } |
| |
| - (NSString*)javaScriptToDispatchPopStateWithObject:(NSString*)stateObjectJSON { |
| std::string outState; |
| base::EscapeJSONString(base::SysNSStringToUTF8(stateObjectJSON), true, |
| &outState); |
| return [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);", |
| base::SysUTF8ToNSString(outState)]; |
| } |
| |
| - (NSString*)javaScriptToDispatchHashChangeWithOldURL:(const GURL&)oldURL |
| newURL:(const GURL&)newURL { |
| return [NSString |
| stringWithFormat:@"__gCrWeb.dispatchHashchangeEvent(\'%s\', \'%s\');", |
| oldURL.spec().c_str(), newURL.spec().c_str()]; |
| } |
| |
| - (void)injectHTML5HistoryScriptWithHashChange:(BOOL)dispatchHashChange |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation { |
| web::NavigationItemImpl* currentItem = self.currentNavItem; |
| if (!currentItem) |
| return; |
| |
| const GURL URL = currentItem->GetURL(); |
| NSString* stateObject = currentItem->GetSerializedStateObject(); |
| NSMutableString* script = [NSMutableString |
| stringWithString:[self javaScriptToReplaceWebViewURL:URL |
| stateObjectJSON:stateObject]]; |
| if (sameDocumentNavigation) { |
| [script |
| appendString:[self javaScriptToDispatchPopStateWithObject:stateObject]]; |
| } |
| if (dispatchHashChange) { |
| web::NavigationItemImpl* previousItem = self.sessionController.previousItem; |
| const GURL oldURL = previousItem ? previousItem->GetURL() : GURL(); |
| [script appendString:[self javaScriptToDispatchHashChangeWithOldURL:oldURL |
| newURL:URL]]; |
| } |
| [self executeJavaScript:script completionHandler:nil]; |
| } |
| |
| // Load the current URL in a web view, first ensuring the web view is visible. |
| - (void)loadCurrentURLInWebView { |
| web::NavigationItem* item = self.currentNavItem; |
| GURL targetURL = item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| // Load the url. The UIWebView delegate callbacks take care of updating the |
| // session history and UI. |
| if (!targetURL.is_valid()) { |
| [self didFinishWithURL:targetURL loadSuccess:NO context:nullptr]; |
| _webStateImpl->SetIsLoading(false); |
| _webStateImpl->OnPageLoaded(targetURL, NO); |
| return; |
| } |
| |
| // JavaScript should never be evaluated here. User-entered JS should be |
| // evaluated via stringByEvaluatingUserJavaScriptFromString. |
| DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme)); |
| |
| [self ensureWebViewCreated]; |
| |
| [self loadRequestForCurrentNavigationItem]; |
| } |
| |
| - (void)updatePendingNavigationInfoFromNavigationAction: |
| (WKNavigationAction*)action { |
| if (action.targetFrame.mainFrame) { |
| _pendingNavigationInfo = |
| [[CRWWebControllerPendingNavigationInfo alloc] init]; |
| [_pendingNavigationInfo |
| setReferrer:[self referrerFromNavigationAction:action]]; |
| [_pendingNavigationInfo setNavigationType:action.navigationType]; |
| [_pendingNavigationInfo setHTTPMethod:action.request.HTTPMethod]; |
| BOOL hasUserGesture = web::GetNavigationActionInitiationType(action) == |
| web::NavigationActionInitiationType::kUserInitiated; |
| [_pendingNavigationInfo setHasUserGesture:hasUserGesture]; |
| } |
| } |
| |
| - (void)updatePendingNavigationInfoFromNavigationResponse: |
| (WKNavigationResponse*)response { |
| if (response.isForMainFrame) { |
| if (!_pendingNavigationInfo) { |
| _pendingNavigationInfo = |
| [[CRWWebControllerPendingNavigationInfo alloc] init]; |
| } |
| [_pendingNavigationInfo setMIMEType:response.response.MIMEType]; |
| } |
| } |
| |
| - (void)commitPendingNavigationInfo { |
| if ([_pendingNavigationInfo referrer]) { |
| _currentReferrerString = [[_pendingNavigationInfo referrer] copy]; |
| } |
| if ([_pendingNavigationInfo MIMEType]) { |
| self.webStateImpl->SetContentsMimeType( |
| base::SysNSStringToUTF8([_pendingNavigationInfo MIMEType])); |
| } |
| [self updateCurrentBackForwardListItemHolder]; |
| |
| _pendingNavigationInfo = nil; |
| } |
| |
| - (NSMutableURLRequest*)requestForCurrentNavigationItem { |
| web::NavigationItem* item = self.currentNavItem; |
| const GURL currentNavigationURL = item ? item->GetURL() : GURL::EmptyGURL(); |
| NSMutableURLRequest* request = [NSMutableURLRequest |
| requestWithURL:net::NSURLWithGURL(currentNavigationURL)]; |
| const web::Referrer referrer(self.currentNavItemReferrer); |
| if (referrer.url.is_valid()) { |
| std::string referrerValue = |
| web::ReferrerHeaderValueForNavigation(currentNavigationURL, referrer); |
| if (!referrerValue.empty()) { |
| [request setValue:base::SysUTF8ToNSString(referrerValue) |
| forHTTPHeaderField:kReferrerHeaderName]; |
| } |
| } |
| |
| // If there are headers in the current session entry add them to |request|. |
| // Headers that would overwrite fields already present in |request| are |
| // skipped. |
| NSDictionary* headers = self.currentHTTPHeaders; |
| for (NSString* headerName in headers) { |
| if (![request valueForHTTPHeaderField:headerName]) { |
| [request setValue:[headers objectForKey:headerName] |
| forHTTPHeaderField:headerName]; |
| } |
| } |
| |
| return request; |
| } |
| |
| - (web::WKBackForwardListItemHolder*)currentBackForwardListItemHolder { |
| web::NavigationItem* item = self.currentNavItem; |
| DCHECK(item); |
| web::WKBackForwardListItemHolder* holder = |
| web::WKBackForwardListItemHolder::FromNavigationItem(item); |
| DCHECK(holder); |
| return holder; |
| } |
| |
| - (void)updateCurrentBackForwardListItemHolder { |
| // WebUI pages (which are loaded via loadHTMLString:baseURL:) have no entry |
| // in the back/forward list, so the current item will still be the previous |
| // page, and should not be associated. |
| if (_webUIManager) |
| return; |
| |
| if (!self.currentNavItem) { |
| // TODO(crbug.com/925304): Pending item (which stores the holder) should be |
| // owned by NavigationContext object. Pending item should never be null. |
| return; |
| } |
| |
| web::WKBackForwardListItemHolder* holder = |
| [self currentBackForwardListItemHolder]; |
| |
| WKNavigationType navigationType = |
| _pendingNavigationInfo ? [_pendingNavigationInfo navigationType] |
| : WKNavigationTypeOther; |
| holder->set_back_forward_list_item([_webView backForwardList].currentItem); |
| holder->set_navigation_type(navigationType); |
| holder->set_http_method([_pendingNavigationInfo HTTPMethod]); |
| |
| // Only update the MIME type in the holder if there was MIME type information |
| // as part of this pending load. It will be nil when doing a fast |
| // back/forward navigation, for instance, because the callback that would |
| // populate it is not called in that flow. |
| if ([_pendingNavigationInfo MIMEType]) |
| holder->set_mime_type([_pendingNavigationInfo MIMEType]); |
| } |
| |
| - (void)loadErrorPageForNavigationItem:(web::NavigationItemImpl*)item |
| navigationContext:(web::NavigationContextImpl*)context { |
| const GURL currentURL = item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| NSError* error = context->GetError(); |
| DCHECK(error); |
| DCHECK_EQ(item->GetUniqueID(), context->GetNavigationItemUniqueID()); |
| |
| if (web::IsWKWebViewSSLCertError(error)) { |
| // This could happen only if certificate is absent or could not be parsed. |
| error = web::NetErrorFromError(error, net::ERR_SSL_SERVER_CERT_BAD_FORMAT); |
| #if defined(DEBUG) |
| net::SSLInfo info; |
| web::GetSSLInfoFromWKWebViewSSLCertError(error, &info); |
| CHECK(!error.cert); |
| #endif |
| } else { |
| error = web::NetErrorFromError(error); |
| } |
| |
| NSString* errorHTML = nil; |
| web::GetWebClient()->PrepareErrorPage( |
| error, context->IsPost(), |
| _webStateImpl->GetBrowserState()->IsOffTheRecord(), &errorHTML); |
| |
| WKNavigation* navigation = |
| [_webView loadHTMLString:errorHTML |
| baseURL:net::NSURLWithGURL(currentURL)]; |
| |
| auto loadHTMLContext = web::NavigationContextImpl::CreateNavigationContext( |
| _webStateImpl, currentURL, |
| /*has_user_gesture=*/false, ui::PAGE_TRANSITION_FIRST, |
| /*is_renderer_initiated=*/false); |
| loadHTMLContext->SetLoadingErrorPage(true); |
| loadHTMLContext->SetNavigationItemUniqueID(item->GetUniqueID()); |
| |
| [_navigationStates setContext:std::move(loadHTMLContext) |
| forNavigation:navigation]; |
| [_navigationStates setState:web::WKNavigationState::REQUESTED |
| forNavigation:navigation]; |
| |
| // TODO(crbug.com/803503): only call these for placeholder navigation because |
| // they should have already been triggered during navigation commit for |
| // failures that happen after commit. |
| [self didStartLoading]; |
| self.navigationManagerImpl->CommitPendingItem(); |
| web::NavigationItem* lastCommittedItem = |
| self.navigationManagerImpl->GetLastCommittedItem(); |
| [self setDocumentURL:lastCommittedItem->GetURL() context:context]; |
| |
| // If |context| is a placeholder navigation, this is the second part of the |
| // error page load for a provisional load failure. Rewrite the context URL to |
| // actual URL and trigger the deferred |OnNavigationFinished| callback. This |
| // is also needed if |context| is not yet committed, which can happen on a |
| // reload/back/forward load that failed in provisional navigation. |
| if (context->IsPlaceholderNavigation() || !context->HasCommitted()) { |
| context->SetUrl(item->GetURL()); |
| context->SetPlaceholderNavigation(false); |
| context->SetHasCommitted(true); |
| _webStateImpl->OnNavigationFinished(context); |
| } |
| |
| [self loadCompleteWithSuccess:NO forContext:context]; |
| _webStateImpl->SetIsLoading(false); |
| _webStateImpl->OnPageLoaded(currentURL, NO); |
| } |
| |
| // Loads the current URL in a native controller if using the legacy navigation |
| // stack. If the new navigation stack is used, start loading a placeholder |
| // into the web view, upon the completion of which the native controller will |
| // be triggered. |
| - (void)loadCurrentURLInNativeView { |
| if (!web::GetWebClient()->IsSlimNavigationManagerEnabled()) { |
| // Free the web view. |
| [self removeWebView]; |
| [self presentNativeContentForNavigationItem:self.currentNavItem]; |
| [self didLoadNativeContentForNavigationItem:self.currentNavItem]; |
| } else { |
| // Just present the native view now. Leave the rest of native content load |
| // until the placeholder navigation finishes. |
| [self presentNativeContentForNavigationItem:self.currentNavItem]; |
| web::NavigationContextImpl* context = [self |
| loadPlaceholderInWebViewForURL:self.currentNavItem->GetVirtualURL() |
| forContext:nullptr]; |
| context->SetIsNativeContentPresented(true); |
| } |
| } |
| |
| - (void)presentNativeContentForNavigationItem:(web::NavigationItem*)item { |
| const GURL targetURL = item ? item->GetURL() : GURL::EmptyGURL(); |
| id<CRWNativeContent> nativeContent = |
| [_nativeProvider controllerForURL:targetURL webState:self.webState]; |
| // Unlike the WebView case, always create a new controller and view. |
| // TODO(crbug.com/759178): What to do if this does return nil? |
| [self setNativeController:nativeContent]; |
| if ([nativeContent respondsToSelector:@selector(virtualURL)]) { |
| item->SetVirtualURL([nativeContent virtualURL]); |
| } |
| |
| NSString* title = [self.nativeController title]; |
| if (title && item) { |
| base::string16 newTitle = base::SysNSStringToUTF16(title); |
| item->SetTitle(newTitle); |
| } |
| } |
| |
| - (void)didLoadNativeContentForNavigationItem:(web::NavigationItemImpl*)item { |
| const GURL targetURL = item ? item->GetURL() : GURL::EmptyGURL(); |
| const web::Referrer referrer; |
| std::unique_ptr<web::NavigationContextImpl> context = |
| [self registerLoadRequestForURL:targetURL |
| referrer:referrer |
| transition:self.currentTransition |
| sameDocumentNavigation:NO |
| hasUserGesture:YES |
| placeholderNavigation:NO]; |
| |
| _webStateImpl->OnNavigationStarted(context.get()); |
| [self didStartLoading]; |
| self.navigationManagerImpl->CommitPendingItem(); |
| context->SetHasCommitted(true); |
| _webStateImpl->OnNavigationFinished(context.get()); |
| |
| if (item && web::GetWebClient()->IsAppSpecificURL(item->GetURL())) { |
| // Report the successful navigation to the ErrorRetryStateMachine. |
| item->error_retry_state_machine().SetNoNavigationError(); |
| } |
| |
| NSString* title = [self.nativeController title]; |
| if (title) { |
| [self setNavigationItemTitle:title]; |
| } |
| |
| if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) { |
| [self.nativeController setDelegate:self]; |
| } |
| |
| _loadPhase = web::PAGE_LOADED; |
| [self didFinishWithURL:targetURL loadSuccess:YES context:context.get()]; |
| } |
| |
| - (web::NavigationContextImpl*) |
| loadPlaceholderInWebViewForURL:(const GURL&)originalURL |
| forContext:(std::unique_ptr<web::NavigationContextImpl>) |
| originalContext { |
| GURL placeholderURL = CreatePlaceholderUrlForUrl(originalURL); |
| [self ensureWebViewCreated]; |
| |
| NSURLRequest* request = |
| [NSURLRequest requestWithURL:net::NSURLWithGURL(placeholderURL)]; |
| WKNavigation* navigation = [_webView loadRequest:request]; |
| [_navigationStates setState:web::WKNavigationState::REQUESTED |
| forNavigation:navigation]; |
| std::unique_ptr<web::NavigationContextImpl> navigationContext; |
| if (originalContext) { |
| navigationContext = std::move(originalContext); |
| navigationContext->SetPlaceholderNavigation(YES); |
| } else { |
| navigationContext = [self registerLoadRequestForURL:originalURL |
| sameDocumentNavigation:NO |
| hasUserGesture:NO |
| placeholderNavigation:YES]; |
| } |
| [_navigationStates setContext:std::move(navigationContext) |
| forNavigation:navigation]; |
| return [_navigationStates contextForNavigation:navigation]; |
| } |
| |
| - (void)handleErrorRetryCommand:(web::ErrorRetryCommand)command |
| navigationItem:(web::NavigationItemImpl*)item |
| navigationContext:(web::NavigationContextImpl*)context |
| originalNavigation:(WKNavigation*)originalNavigation { |
| if (command == web::ErrorRetryCommand::kDoNothing) |
| return; |
| |
| DCHECK_EQ(item->GetUniqueID(), context->GetNavigationItemUniqueID()); |
| switch (command) { |
| case web::ErrorRetryCommand::kLoadPlaceholder: { |
| // This case only happens when a new request failed in provisional |
| // navigation. Disassociate the navigation context from the original |
| // request and resuse it for the placeholder navigation. |
| std::unique_ptr<web::NavigationContextImpl> originalContext = |
| [_navigationStates removeNavigation:originalNavigation]; |
| [self loadPlaceholderInWebViewForURL:item->GetURL() |
| forContext:std::move(originalContext)]; |
| } break; |
| |
| case web::ErrorRetryCommand::kLoadErrorView: |
| [self loadErrorPageForNavigationItem:item navigationContext:context]; |
| break; |
| |
| case web::ErrorRetryCommand::kReload: |
| [_webView reload]; |
| break; |
| |
| case web::ErrorRetryCommand::kRewriteWebViewURL: { |
| std::unique_ptr<web::NavigationContextImpl> navigationContext = |
| [self registerLoadRequestForURL:item->GetURL() |
| sameDocumentNavigation:NO |
| hasUserGesture:NO |
| placeholderNavigation:NO]; |
| WKNavigation* navigation = |
| [_webView loadHTMLString:@"" |
| baseURL:net::NSURLWithGURL(item->GetURL())]; |
| navigationContext->SetError(context->GetError()); |
| navigationContext->SetIsPost(context->IsPost()); |
| [_navigationStates setContext:std::move(navigationContext) |
| forNavigation:navigation]; |
| } break; |
| |
| case web::ErrorRetryCommand::kDoNothing: |
| NOTREACHED(); |
| } |
| } |
| |
| - (void)loadCurrentURL { |
| // If the content view doesn't exist, the tab has either been evicted, or |
| // never displayed. Bail, and let the URL be loaded when the tab is shown. |
| if (!_containerView) |
| return; |
| |
| // WKBasedNavigationManagerImpl needs WKWebView to load native views, but |
| // WKWebView cannot be created while web usage is disabled to avoid breaking |
| // clearing browser data. Bail now and let the URL be loaded when web |
| // usage is enabled again. This can happen when purging web pages when an |
| // interstitial is presented over a native view. See https://crbug.com/865985 |
| // for details. |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && |
| !_webUsageEnabled) |
| return; |
| |
| _currentURLLoadWasTrigerred = YES; |
| |
| // Reset current WebUI if one exists. |
| [self clearWebUI]; |
| |
| // Abort any outstanding page load. This ensures the delegate gets informed |
| // about the outgoing page, and further messages from the page are suppressed. |
| if (_loadPhase != web::PAGE_LOADED) |
| [self abortLoad]; |
| |
| DCHECK(!_isHalted); |
| _webStateImpl->ClearTransientContent(); |
| |
| web::NavigationItem* item = self.currentNavItem; |
| const GURL currentURL = item ? item->GetURL() : GURL::EmptyGURL(); |
| const bool isCurrentURLAppSpecific = |
| web::GetWebClient()->IsAppSpecificURL(currentURL); |
| // If it's a chrome URL, but not a native one, create the WebUI instance. |
| if (isCurrentURLAppSpecific && |
| ![_nativeProvider hasControllerForURL:currentURL]) { |
| if (!(item->GetTransitionType() & ui::PAGE_TRANSITION_TYPED || |
| item->GetTransitionType() & ui::PAGE_TRANSITION_AUTO_BOOKMARK) && |
| self.hasOpener) { |
| // WebUI URLs can not be opened by DOM to prevent cross-site scripting as |
| // they have increased power. WebUI URLs may only be opened when the user |
| // types in the URL or use bookmarks. |
| self.navigationManagerImpl->DiscardNonCommittedItems(); |
| return; |
| } else { |
| [self createWebUIForURL:currentURL]; |
| } |
| } |
| |
| // Loading a new url, must check here if it's a native chrome URL and |
| // replace the appropriate view if so, or transition back to a web view from |
| // a native view. |
| if ([self shouldLoadURLInNativeView:currentURL]) { |
| [self loadCurrentURLInNativeView]; |
| } else if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && |
| isCurrentURLAppSpecific && _webStateImpl->HasWebUI()) { |
| [self loadPlaceholderInWebViewForURL:currentURL forContext:nullptr]; |
| } else { |
| [self loadCurrentURLInWebView]; |
| } |
| } |
| |
| - (void)loadCurrentURLIfNecessary { |
| if (_webProcessCrashed) { |
| [self loadCurrentURL]; |
| } else if (!_currentURLLoadWasTrigerred) { |
| [self ensureContainerViewCreated]; |
| |
| // This method reloads last committed item, so make than item also pending. |
| self.sessionController.pendingItemIndex = |
| self.sessionController.lastCommittedItemIndex; |
| |
| // TODO(crbug.com/796608): end the practice of calling |loadCurrentURL| |
| // when it is possible there is no current URL. If the call performs |
| // necessary initialization, break that out. |
| [self loadCurrentURL]; |
| } |
| } |
| |
| - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel { |
| DCHECK(trustLevel); |
| *trustLevel = web::URLVerificationTrustLevel::kAbsolute; |
| // Placeholder URL is an implementation detail. Don't expose it to users of |
| // web layer. |
| if (IsPlaceholderUrl(_documentURL)) |
| return ExtractUrlFromPlaceholderUrl(_documentURL); |
| return _documentURL; |
| } |
| |
| - (BOOL)shouldLoadURLInNativeView:(const GURL&)url { |
| // App-specific URLs that don't require WebUI are loaded in native views. |
| return web::GetWebClient()->IsAppSpecificURL(url) && |
| !_webStateImpl->HasWebUI() && |
| [_nativeProvider hasControllerForURL:url]; |
| } |
| |
| - (void)reloadWithRendererInitiatedNavigation:(BOOL)isRendererInitiated { |
| // Clear last user interaction. |
| // TODO(crbug.com/546337): Move to after the load commits, in the subclass |
| // implementation. This will be inaccurate if the reload fails or is |
| // cancelled. |
| _lastUserInteraction = nullptr; |
| base::RecordAction(base::UserMetricsAction("Reload")); |
| GURL URL = self.currentNavItem->GetURL(); |
| if ([self shouldLoadURLInNativeView:URL]) { |
| std::unique_ptr<web::NavigationContextImpl> navigationContext = [self |
| registerLoadRequestForURL:URL |
| referrer:self.currentNavItemReferrer |
| transition:ui::PageTransition::PAGE_TRANSITION_RELOAD |
| sameDocumentNavigation:NO |
| hasUserGesture:YES |
| placeholderNavigation:NO]; |
| navigationContext->SetIsRendererInitiated(isRendererInitiated); |
| _webStateImpl->OnNavigationStarted(navigationContext.get()); |
| [self didStartLoading]; |
| self.navigationManagerImpl->CommitPendingItem(); |
| [self.nativeController reload]; |
| navigationContext->SetHasCommitted(true); |
| _webStateImpl->OnNavigationFinished(navigationContext.get()); |
| [self loadCompleteWithSuccess:YES forContext:nullptr]; |
| } else { |
| web::NavigationItem* transientItem = |
| self.navigationManagerImpl->GetTransientItem(); |
| if (transientItem) { |
| // If there's a transient item, a reload is considered a new navigation to |
| // the transient item's URL (as on other platforms). |
| NavigationManager::WebLoadParams reloadParams(transientItem->GetURL()); |
| reloadParams.transition_type = ui::PAGE_TRANSITION_RELOAD; |
| reloadParams.extra_headers = |
| [transientItem->GetHttpRequestHeaders() copy]; |
| self.webState->GetNavigationManager()->LoadURLWithParams(reloadParams); |
| } else { |
| self.currentNavItem->SetTransitionType( |
| ui::PageTransition::PAGE_TRANSITION_RELOAD); |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && |
| !web::GetWebClient()->IsAppSpecificURL( |
| net::GURLWithNSURL(_webView.URL))) { |
| // New navigation manager can delegate directly to WKWebView to reload |
| // for non-app-specific URLs. The necessary navigation states will be |
| // updated in WKNavigationDelegate callbacks. |
| WKNavigation* navigation = [_webView reload]; |
| [_navigationStates setState:web::WKNavigationState::REQUESTED |
| forNavigation:navigation]; |
| std::unique_ptr<web::NavigationContextImpl> navigationContext = [self |
| registerLoadRequestForURL:URL |
| referrer:self.currentNavItemReferrer |
| transition:ui::PageTransition::PAGE_TRANSITION_RELOAD |
| sameDocumentNavigation:NO |
| hasUserGesture:YES |
| placeholderNavigation:NO]; |
| [_navigationStates setContext:std::move(navigationContext) |
| forNavigation:navigation]; |
| } else { |
| [self loadCurrentURL]; |
| } |
| } |
| } |
| } |
| |
| - (void)abortLoad { |
| [_webView stopLoading]; |
| [_pendingNavigationInfo setCancelled:YES]; |
| _certVerificationErrors->Clear(); |
| [self loadCancelled]; |
| _safeBrowsingWarningDetectionTimer.Stop(); |
| } |
| |
| - (void)loadCancelled { |
| // TODO(crbug.com/821995): Check if this function should be removed. |
| if (_loadPhase != web::PAGE_LOADED) { |
| _loadPhase = web::PAGE_LOADED; |
| if (!_isHalted) { |
| _webStateImpl->SetIsLoading(false); |
| } |
| } |
| } |
| |
| - (BOOL)contentIsImage { |
| if (!_webView) |
| return NO; |
| |
| const std::string image = "image"; |
| std::string MIMEType = self.webState->GetContentsMimeType(); |
| return MIMEType.compare(0, image.length(), image) == 0; |
| } |
| |
| - (void)didReceiveRedirectForNavigation:(web::NavigationContextImpl*)context |
| withURL:(const GURL&)URL { |
| context->SetUrl(URL); |
| web::NavigationItemImpl* item = web::GetItemWithUniqueID( |
| self.navigationManagerImpl, context->GetNavigationItemUniqueID()); |
| |
| // Associated item can be a pending item, previously discarded by another |
| // navigation. WKWebView allows multiple provisional navigations, while |
| // Navigation Manager has only one pending navigation. |
| if (item) { |
| if (!web::wk_navigation_util::IsWKInternalUrl(URL)) { |
| item->SetVirtualURL(URL); |
| item->SetURL(URL); |
| } |
| // Redirects (3xx response code), must change POST requests to GETs. |
| item->SetPostData(nil); |
| item->ResetHttpRequestHeaders(); |
| } |
| |
| _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent(); |
| } |
| |
| - (void)didFinishNavigation:(web::NavigationContextImpl*)context { |
| // This can be called at multiple times after the document has loaded. Do |
| // nothing if the document has already loaded. |
| if (_loadPhase == web::PAGE_LOADED) |
| return; |
| |
| BOOL success = !context || !context->GetError(); |
| [self loadCompleteWithSuccess:success forContext:context]; |
| } |
| |
| - (void)loadCompleteWithSuccess:(BOOL)loadSuccess |
| forContext:(web::NavigationContextImpl*)context { |
| // The webView may have been torn down (or replaced by a native view). Be |
| // safe and do nothing if that's happened. |
| if (_loadPhase != web::PAGE_LOADING) |
| return; |
| |
| const GURL currentURL([self currentURL]); |
| |
| _loadPhase = web::PAGE_LOADED; |
| |
| [self optOutScrollsToTopForSubviews]; |
| |
| // Perform post-load-finished updates. |
| [self didFinishWithURL:currentURL loadSuccess:loadSuccess context:context]; |
| |
| // Execute the pending LoadCompleteActions. |
| for (ProceduralBlock action in _pendingLoadCompleteActions) { |
| action(); |
| } |
| [_pendingLoadCompleteActions removeAllObjects]; |
| } |
| |
| - (void)didFinishWithURL:(const GURL&)currentURL |
| loadSuccess:(BOOL)loadSuccess |
| context:(nullable const web::NavigationContextImpl*)context { |
| DCHECK(_loadPhase == web::PAGE_LOADED); |
| // Rather than creating a new WKBackForwardListItem when loading WebUI pages, |
| // WKWebView will cache the WebUI HTML in the previous WKBackForwardListItem |
| // since it's loaded via |-loadHTML:forURL:| instead of an NSURLRequest. As a |
| // result, the WebUI's HTML and URL will be loaded when navigating to that |
| // WKBackForwardListItem, causing a mismatch between the visible content and |
| // the visible URL (WebUI page will be visible, but URL will be the previous |
| // page's URL). To prevent this potential URL spoofing vulnerability, reset |
| // the previous NavigationItem's WKBackForwardListItem to force loading via |
| // NSURLRequest. |
| if (_webUIManager) { |
| web::NavigationItem* lastNavigationItem = |
| self.sessionController.previousItem; |
| if (lastNavigationItem) { |
| web::WKBackForwardListItemHolder* holder = |
| web::WKBackForwardListItemHolder::FromNavigationItem( |
| lastNavigationItem); |
| DCHECK(holder); |
| holder->set_back_forward_list_item(nil); |
| } |
| } |
| |
| [self restoreStateFromHistory]; |
| |
| // Placeholder and restore session URLs are implementation details so should |
| // not notify WebStateObservers. If |context| is nullptr, don't skip |
| // placeholder URLs because this may be the only opportunity to update |
| // |isLoading| for native view reload. |
| |
| if (context && context->IsPlaceholderNavigation()) |
| return; |
| |
| if (context && IsRestoreSessionUrl(context->GetUrl())) |
| return; |
| |
| if (IsRestoreSessionUrl(net::GURLWithNSURL(_webView.URL))) |
| return; |
| |
| if (context && context->IsLoadingErrorPage()) |
| return; |
| |
| if (!loadSuccess) { |
| // WebStateObserver callbacks will be called for load failure after |
| // loading placeholder URL. |
| return; |
| } |
| |
| _webStateImpl->SetIsLoading(false); |
| _webStateImpl->OnPageLoaded(currentURL, YES); |
| } |
| |
| - (void)rendererInitiatedGoDelta:(int)delta |
| hasUserGesture:(BOOL)hasUserGesture { |
| if (_isBeingDestroyed) |
| return; |
| |
| if (delta == 0) { |
| [self reloadWithRendererInitiatedNavigation:YES]; |
| return; |
| } |
| |
| if (self.navigationManagerImpl->CanGoToOffset(delta)) { |
| int index = self.navigationManagerImpl->GetIndexForOffset(delta); |
| self.navigationManagerImpl->GoToIndex( |
| index, web::NavigationInitiationType::RENDERER_INITIATED, |
| /*has_user_gesture=*/hasUserGesture); |
| } |
| } |
| |
| - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer { |
| if ([_gestureRecognizers containsObject:recognizer]) |
| return; |
| |
| [_webView addGestureRecognizer:recognizer]; |
| [_gestureRecognizers addObject:recognizer]; |
| } |
| |
| - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer { |
| if (![_gestureRecognizers containsObject:recognizer]) |
| return; |
| |
| [_webView removeGestureRecognizer:recognizer]; |
| [_gestureRecognizers removeObject:recognizer]; |
| } |
| |
| - (CRWJSInjectionReceiver*)jsInjectionReceiver { |
| return _jsInjectionReceiver; |
| } |
| |
| - (BOOL)shouldClosePageOnNativeApplicationLoad { |
| // The page should be closed if it was initiated by the DOM and there has been |
| // no user interaction with the page since the web view was created, or if |
| // the page has no navigation items, as occurs when an App Store link is |
| // opened from another application. |
| BOOL rendererInitiatedWithoutInteraction = |
| self.hasOpener && !_userInteractedWithWebController; |
| BOOL noNavigationItems = !(self.navigationManagerImpl->GetItemCount()); |
| return rendererInitiatedWithoutInteraction || noNavigationItems; |
| } |
| |
| - (web::UserAgentType)userAgentType { |
| web::NavigationItem* item = self.currentNavItem; |
| return item ? item->GetUserAgentType() : web::UserAgentType::MOBILE; |
| } |
| |
| - (web::MojoFacade*)mojoFacade { |
| if (!_mojoFacade) { |
| service_manager::mojom::InterfaceProvider* interfaceProvider = |
| _webStateImpl->GetWebStateInterfaceProvider(); |
| _mojoFacade.reset(new web::MojoFacade(interfaceProvider, self)); |
| } |
| return _mojoFacade.get(); |
| } |
| |
| - (void)updateDesktopUserAgentForItem:(web::NavigationItem*)item |
| previousUserAgentType:(web::UserAgentType)userAgentType { |
| if (!item) |
| return; |
| web::UserAgentType itemUserAgentType = item->GetUserAgentType(); |
| if (itemUserAgentType == web::UserAgentType::NONE) |
| return; |
| if (itemUserAgentType != userAgentType) |
| [self requirePageReconstruction]; |
| } |
| |
| - (void)discardNonCommittedItemsIfLastCommittedWasNotNativeView { |
| GURL lastCommittedURL = self.webState->GetLastCommittedURL(); |
| BOOL previousItemWasLoadedInNativeView = |
| [self shouldLoadURLInNativeView:lastCommittedURL]; |
| if (!previousItemWasLoadedInNativeView) |
| self.navigationManagerImpl->DiscardNonCommittedItems(); |
| } |
| |
| - (void)takeSnapshotWithRect:(CGRect)rect |
| completion:(void (^)(UIImage*))completion { |
| if (@available(iOS 11, *)) { |
| if (_webView) { |
| WKSnapshotConfiguration* configuration = |
| [[WKSnapshotConfiguration alloc] init]; |
| configuration.rect = [_webView convertRect:rect fromView:self.view]; |
| __weak CRWWebController* weakSelf = self; |
| [_webView |
| takeSnapshotWithConfiguration:configuration |
| completionHandler:^(UIImage* snapshot, NSError* error) { |
| // Pass nil to the completion block if there is an error |
| // or if the web view has been removed before the |
| // snapshot is finished. |snapshot| can sometimes be |
| // corrupt if it's sent due to the WKWebView's |
| // deallocation, so callbacks received after |
| // |-removeWebView| are ignored to prevent crashing. |
| if (error || !weakSelf.webView) { |
| if (error) { |
| DLOG(ERROR) << "WKWebView snapshot error: " |
| << error.description; |
| } |
| completion(nil); |
| } else { |
| completion(snapshot); |
| } |
| }]; |
| return; |
| } |
| } |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| completion(nil); |
| }); |
| } |
| |
| #pragma mark - |
| #pragma mark CRWWebControllerContainerViewDelegate |
| |
| - (CRWWebViewProxyImpl*)contentViewProxyForContainerView: |
| (CRWWebControllerContainerView*)containerView { |
| return _webViewProxy; |
| } |
| |
| - (UIEdgeInsets)nativeContentInsetsForContainerView: |
| (CRWWebControllerContainerView*)containerView { |
| return [self.nativeProvider nativeContentInsetForWebState:self.webState]; |
| } |
| |
| #pragma mark - |
| #pragma mark CRWJSInjectionEvaluator Methods |
| |
| - (void)executeJavaScript:(NSString*)script |
| completionHandler:(web::JavaScriptResultBlock)completionHandler { |
| NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script]; |
| web::ExecuteJavaScript(_webView, safeScript, completionHandler); |
| } |
| |
| - (BOOL)scriptHasBeenInjectedForClass:(Class)injectionManagerClass { |
| return [_injectedScriptManagers containsObject:injectionManagerClass]; |
| } |
| |
| - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass { |
| DCHECK(script.length); |
| // Script execution is an asynchronous operation which may pass sensitive |
| // data to the page. executeJavaScript:completionHandler makes sure that |
| // receiver page did not change by checking its window id. |
| // |[_webView executeJavaScript:completionHandler:]| is not used here because |
| // it does not check that page is the same. |
| [self executeJavaScript:script completionHandler:nil]; |
| [_injectedScriptManagers addObject:JSInjectionManagerClass]; |
| } |
| |
| #pragma mark - |
| |
| - (void)executeUserJavaScript:(NSString*)script |
| completionHandler:(web::JavaScriptResultBlock)completion { |
| // For security reasons, executing JavaScript on pages with app-specific URLs |
| // is not allowed, because those pages may have elevated privileges. |
| GURL lastCommittedURL = self.webState->GetLastCommittedURL(); |
| if (web::GetWebClient()->IsAppSpecificURL(lastCommittedURL)) { |
| if (completion) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| NSError* error = [[NSError alloc] |
| initWithDomain:web::kJSEvaluationErrorDomain |
| code:web::JS_EVALUATION_ERROR_CODE_NO_WEB_VIEW |
| userInfo:nil]; |
| completion(nil, error); |
| }); |
| } |
| return; |
| } |
| |
| [self touched:YES]; |
| [self executeJavaScript:script completionHandler:completion]; |
| } |
| |
| - (BOOL)respondToMessage:(base::DictionaryValue*)message |
| userIsInteracting:(BOOL)userIsInteracting |
| originURL:(const GURL&)originURL |
| isMainFrame:(BOOL)isMainFrame |
| senderFrame:(web::WebFrame*)senderFrame { |
| std::string command; |
| if (!message->GetString("command", &command)) { |
| DLOG(WARNING) << "JS message parameter not found: command"; |
| return NO; |
| } |
| |
| SEL handler = [self selectorToHandleJavaScriptCommand:command]; |
| if (!handler) { |
| if (self.webStateImpl->OnScriptCommandReceived(command, *message, originURL, |
| userIsInteracting, |
| isMainFrame, senderFrame)) { |
| return YES; |
| } |
| // Message was either unexpected or not correctly handled. |
| // Page is reset as a precaution. |
| DLOG(WARNING) << "Unexpected message received: " << command; |
| return NO; |
| } |
| |
| typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*); |
| HandlerType handlerImplementation = |
| reinterpret_cast<HandlerType>([self methodForSelector:handler]); |
| DCHECK(handlerImplementation); |
| NSMutableDictionary* context = |
| [NSMutableDictionary dictionaryWithObject:@(userIsInteracting) |
| forKey:kUserIsInteractingKey]; |
| NSURL* originNSURL = net::NSURLWithGURL(originURL); |
| if (originNSURL) |
| context[kOriginURLKey] = originNSURL; |
| context[kIsMainFrame] = @(isMainFrame); |
| return handlerImplementation(self, handler, message, context); |
| } |
| |
| - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command { |
| static std::map<std::string, SEL>* handlers = nullptr; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| handlers = new std::map<std::string, SEL>(); |
| (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:); |
| (*handlers)["document.favicons"] = |
| @selector(handleDocumentFaviconsMessage:context:); |
| (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:); |
| (*handlers)["window.hashchange"] = |
| @selector(handleWindowHashChangeMessage:context:); |
| (*handlers)["window.history.back"] = |
| @selector(handleWindowHistoryBackMessage:context:); |
| (*handlers)["window.history.willChangeState"] = |
| @selector(handleWindowHistoryWillChangeStateMessage:context:); |
| (*handlers)["window.history.didPushState"] = |
| @selector(handleWindowHistoryDidPushStateMessage:context:); |
| (*handlers)["window.history.didReplaceState"] = |
| @selector(handleWindowHistoryDidReplaceStateMessage:context:); |
| (*handlers)["window.history.forward"] = |
| @selector(handleWindowHistoryForwardMessage:context:); |
| (*handlers)["window.history.go"] = |
| @selector(handleWindowHistoryGoMessage:context:); |
| (*handlers)["restoresession.error"] = |
| @selector(handleRestoreSessionErrorMessage:context:); |
| }); |
| DCHECK(handlers); |
| auto iter = handlers->find(command); |
| return iter != handlers->end() ? iter->second : nullptr; |
| } |
| |
| - (void)didReceiveScriptMessage:(WKScriptMessage*)message { |
| // Broken out into separate method to catch errors. |
| if (![self respondToWKScriptMessage:message]) { |
| DLOG(WARNING) << "Message from JS not handled due to invalid format"; |
| } |
| } |
| |
| - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script { |
| NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }"; |
| return [NSString |
| stringWithFormat:kTemplate, [_windowIDJSManager windowID], script]; |
| } |
| |
| - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage { |
| if (![scriptMessage.name isEqualToString:kScriptMessageName]) { |
| return NO; |
| } |
| |
| std::unique_ptr<base::Value> messageAsValue = |
| web::ValueResultFromWKResult(scriptMessage.body); |
| base::DictionaryValue* message = nullptr; |
| if (!messageAsValue || !messageAsValue->GetAsDictionary(&message)) { |
| return NO; |
| } |
| |
| web::WebFrame* senderFrame = nullptr; |
| std::string frameID; |
| if (message->GetString("crwFrameId", &frameID)) { |
| senderFrame = web::GetWebFrameWithId([self webState], frameID); |
| } |
| |
| if (base::FeatureList::IsEnabled(web::features::kWebFrameMessaging)) { |
| // Message must be associated with a current frame. |
| if (!senderFrame) { |
| return NO; |
| } |
| } else { |
| GURL messageFrameOrigin = web::GURLOriginWithWKSecurityOrigin( |
| scriptMessage.frameInfo.securityOrigin); |
| if (!scriptMessage.frameInfo.mainFrame && |
| messageFrameOrigin.GetOrigin() != _documentURL.GetOrigin()) { |
| // Messages from cross-origin iframes are not currently supported. |
| // |scriptMessage.frameInfo.securityOrigin| returns opener's origin for |
| // about:blank pages, so it is important to allow all messages coming from |
| // the main frame, even if messageFrameOrigin and _documentURL have |
| // different origins. |
| return NO; |
| } |
| |
| std::string windowID; |
| // If windowID exists, it must match the ID from the main frame. |
| if (message->GetString("crwWindowId", &windowID)) { |
| if (base::SysNSStringToUTF8([_windowIDJSManager windowID]) != windowID) { |
| DLOG(WARNING) |
| << "Message from JS ignored due to non-matching windowID: " |
| << base::SysNSStringToUTF8([_windowIDJSManager windowID]) |
| << " != " << windowID; |
| return NO; |
| } |
| } |
| } |
| |
| base::DictionaryValue* command = nullptr; |
| if (!message->GetDictionary("crwCommand", &command)) { |
| return NO; |
| } |
| return [self respondToMessage:command |
| userIsInteracting:[self userIsInteracting] |
| originURL:net::GURLWithNSURL([_webView URL]) |
| isMainFrame:scriptMessage.frameInfo.mainFrame |
| senderFrame:senderFrame]; |
| } |
| |
| #pragma mark - |
| #pragma mark Web frames management |
| |
| - (void)frameBecameAvailableWithMessage:(WKScriptMessage*)message { |
| // Validate all expected message components because any frame could falsify |
| // this message. |
| // TODO(crbug.com/881816): Create a WebFrame even if key is empty. |
| if (_isBeingDestroyed || ![message.body isKindOfClass:[NSDictionary class]] || |
| ![message.body[@"crwFrameId"] isKindOfClass:[NSString class]]) { |
| // WebController is being destroyed or the message is invalid. |
| return; |
| } |
| |
| std::string frameID = base::SysNSStringToUTF8(message.body[@"crwFrameId"]); |
| web::WebFramesManagerImpl* framesManager = |
| web::WebFramesManagerImpl::FromWebState([self webState]); |
| if (!framesManager->GetFrameWithId(frameID)) { |
| GURL messageFrameOrigin = |
| web::GURLOriginWithWKSecurityOrigin(message.frameInfo.securityOrigin); |
| |
| std::unique_ptr<crypto::SymmetricKey> frameKey; |
| if ([message.body[@"crwFrameKey"] isKindOfClass:[NSString class]] && |
| [message.body[@"crwFrameKey"] length] > 0) { |
| std::string decodedFrameKeyString; |
| std::string encodedFrameKeyString = |
| base::SysNSStringToUTF8(message.body[@"crwFrameKey"]); |
| base::Base64Decode(encodedFrameKeyString, &decodedFrameKeyString); |
| frameKey = crypto::SymmetricKey::Import( |
| crypto::SymmetricKey::Algorithm::AES, decodedFrameKeyString); |
| } |
| |
| auto newFrame = std::make_unique<web::WebFrameImpl>( |
| frameID, message.frameInfo.mainFrame, messageFrameOrigin, |
| self.webState); |
| if (frameKey) { |
| newFrame->SetEncryptionKey(std::move(frameKey)); |
| } |
| |
| NSNumber* lastSentMessageID = |
| message.body[@"crwFrameLastReceivedMessageId"]; |
| if ([lastSentMessageID isKindOfClass:[NSNumber class]]) { |
| int nextMessageID = std::max(0, lastSentMessageID.intValue + 1); |
| newFrame->SetNextMessageId(nextMessageID); |
| } |
| |
| framesManager->AddFrame(std::move(newFrame)); |
| _webStateImpl->OnWebFrameAvailable(framesManager->GetFrameWithId(frameID)); |
| } |
| } |
| |
| - (void)frameBecameUnavailableWithMessage:(WKScriptMessage*)message { |
| if (_isBeingDestroyed || ![message.body isKindOfClass:[NSString class]]) { |
| // WebController is being destroyed or message is invalid. |
| return; |
| } |
| std::string frameID = base::SysNSStringToUTF8(message.body); |
| web::WebFramesManagerImpl* framesManager = |
| web::WebFramesManagerImpl::FromWebState([self webState]); |
| |
| if (framesManager->GetFrameWithId(frameID)) { |
| _webStateImpl->OnWebFrameUnavailable( |
| framesManager->GetFrameWithId(frameID)); |
| framesManager->RemoveFrameWithId(frameID); |
| } |
| } |
| |
| - (void)removeAllWebFrames { |
| web::WebFramesManagerImpl* framesManager = |
| web::WebFramesManagerImpl::FromWebState([self webState]); |
| for (auto* frame : framesManager->GetAllWebFrames()) { |
| _webStateImpl->OnWebFrameUnavailable(frame); |
| } |
| framesManager->RemoveAllWebFrames(); |
| } |
| |
| #pragma mark - |
| #pragma mark JavaScript message handlers |
| |
| - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context { |
| // Chrome message are only handled if sent from the main frame. |
| if (![context[kIsMainFrame] boolValue]) |
| return NO; |
| if (_webStateImpl->HasWebUI()) { |
| const GURL currentURL([self currentURL]); |
| if (web::GetWebClient()->IsAppSpecificURL(currentURL)) { |
| std::string messageContent; |
| base::ListValue* arguments = nullptr; |
| if (!message->GetString("message", &messageContent)) { |
| DLOG(WARNING) << "JS message parameter not found: message"; |
| return NO; |
| } |
| if (!message->GetList("arguments", &arguments)) { |
| DLOG(WARNING) << "JS message parameter not found: arguments"; |
| return NO; |
| } |
| // WebFrame messaging is not supported in WebUI (as window.isSecureContext |
| // is false. Pass nullptr as sender_frame. |
| _webStateImpl->OnScriptCommandReceived( |
| messageContent, *message, currentURL, context[kUserIsInteractingKey], |
| [context[kIsMainFrame] boolValue], nullptr); |
| _webStateImpl->ProcessWebUIMessage(currentURL, messageContent, |
| *arguments); |
| return YES; |
| } |
| } |
| |
| DLOG(WARNING) |
| << "chrome.send message not handled because WebUI was not found."; |
| return NO; |
| } |
| |
| - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context { |
| if (![context[kIsMainFrame] boolValue]) |
| return NO; |
| |
| std::vector<web::FaviconURL> URLs; |
| GURL originGURL; |
| id origin = context[kOriginURLKey]; |
| if (origin) { |
| NSURL* originNSURL = base::mac::ObjCCastStrict<NSURL>(origin); |
| originGURL = net::GURLWithNSURL(originNSURL); |
| } |
| if (!web::ExtractFaviconURL(message, originGURL, &URLs)) |
| return NO; |
| |
| if (!URLs.empty()) |
| _webStateImpl->OnFaviconUrlUpdated(URLs); |
| return YES; |
| } |
| |
| - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message |
| context:(NSDictionary*)context { |
| std::string errorMessage; |
| if (!message->GetString("message", &errorMessage)) { |
| DLOG(WARNING) << "JS message parameter not found: message"; |
| return NO; |
| } |
| |