| // Copyright 2013 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/web_state_impl.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #import "ios/web/common/crw_content_view.h" |
| #include "ios/web/common/features.h" |
| #include "ios/web/common/url_util.h" |
| #import "ios/web/js_messaging/crw_js_injector.h" |
| #import "ios/web/js_messaging/web_view_js_utils.h" |
| #import "ios/web/navigation/crw_error_page_helper.h" |
| #import "ios/web/navigation/navigation_context_impl.h" |
| #import "ios/web/navigation/navigation_item_impl.h" |
| #import "ios/web/navigation/navigation_manager_impl.h" |
| #import "ios/web/navigation/session_storage_builder.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #include "ios/web/public/browser_state.h" |
| #include "ios/web/public/favicon/favicon_url.h" |
| #include "ios/web/public/js_messaging/web_frame.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/navigation/web_state_policy_decider.h" |
| #import "ios/web/public/session/crw_navigation_item_storage.h" |
| #import "ios/web/public/session/crw_session_storage.h" |
| #import "ios/web/public/session/serializable_user_data_manager.h" |
| #include "ios/web/public/thread/web_thread.h" |
| #import "ios/web/public/ui/context_menu_params.h" |
| #import "ios/web/public/ui/java_script_dialog_presenter.h" |
| #import "ios/web/public/web_client.h" |
| #import "ios/web/public/web_state_delegate.h" |
| #include "ios/web/public/web_state_observer.h" |
| #include "ios/web/public/webui/web_ui_ios_controller.h" |
| #import "ios/web/session/session_certificate_policy_cache_impl.h" |
| #import "ios/web/web_state/global_web_state_event_tracker.h" |
| #import "ios/web/web_state/policy_decision_state_tracker.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" |
| #include "ios/web/webui/web_ui_ios_controller_factory_registry.h" |
| #include "ios/web/webui/web_ui_ios_impl.h" |
| #include "net/http/http_response_headers.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/image/image.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace web { |
| namespace { |
| // Function used to implement the default WebState getters. |
| web::WebState* ReturnWeakReference(base::WeakPtr<WebStateImpl> weak_web_state) { |
| return weak_web_state.get(); |
| } |
| } // namespace |
| |
| /* static */ |
| std::unique_ptr<WebState> WebState::Create(const CreateParams& params) { |
| return std::make_unique<WebStateImpl>(params); |
| } |
| |
| /* static */ |
| std::unique_ptr<WebState> WebState::CreateWithStorageSession( |
| const CreateParams& params, |
| CRWSessionStorage* session_storage) { |
| DCHECK(session_storage); |
| return base::WrapUnique(new WebStateImpl(params, session_storage)); |
| } |
| |
| WebStateImpl::WebStateImpl(const CreateParams& params) |
| : WebStateImpl(params, nullptr) {} |
| |
| WebStateImpl::WebStateImpl(const CreateParams& params, |
| CRWSessionStorage* session_storage) |
| : delegate_(nullptr), |
| is_loading_(false), |
| is_being_destroyed_(false), |
| web_controller_(nil), |
| web_frames_manager_(*this), |
| created_with_opener_(params.created_with_opener), |
| user_agent_type_(features::UseWebClientDefaultUserAgent() |
| ? UserAgentType::AUTOMATIC |
| : UserAgentType::MOBILE), |
| weak_factory_(this) { |
| navigation_manager_ = std::make_unique<NavigationManagerImpl>(); |
| |
| navigation_manager_->SetDelegate(this); |
| navigation_manager_->SetBrowserState(params.browser_state); |
| // Send creation event and create the web controller. |
| GlobalWebStateEventTracker::GetInstance()->OnWebStateCreated(this); |
| web_controller_ = [[CRWWebController alloc] initWithWebState:this]; |
| |
| // Restore session history last because NavigationManagerImpl relies on |
| // CRWWebController to restore history into the web view. |
| if (session_storage) { |
| RestoreSessionStorage(session_storage); |
| } else { |
| certificate_policy_cache_ = |
| std::make_unique<SessionCertificatePolicyCacheImpl>(GetBrowserState()); |
| } |
| } |
| |
| WebStateImpl::~WebStateImpl() { |
| is_being_destroyed_ = true; |
| [web_controller_ close]; |
| |
| // WebUI depends on web state so it must be destroyed first in case any WebUI |
| // implementations depends on accessing web state during destruction. |
| ClearWebUI(); |
| |
| for (auto& observer : observers_) |
| observer.WebStateDestroyed(this); |
| for (auto& observer : policy_deciders_) |
| observer.WebStateDestroyed(); |
| for (auto& observer : policy_deciders_) |
| observer.ResetWebState(); |
| SetDelegate(nullptr); |
| } |
| |
| WebState::Getter WebStateImpl::CreateDefaultGetter() { |
| return base::BindRepeating(&ReturnWeakReference, weak_factory_.GetWeakPtr()); |
| } |
| |
| WebState::OnceGetter WebStateImpl::CreateDefaultOnceGetter() { |
| return base::BindOnce(&ReturnWeakReference, weak_factory_.GetWeakPtr()); |
| } |
| |
| WebStateDelegate* WebStateImpl::GetDelegate() { |
| return delegate_; |
| } |
| |
| void WebStateImpl::SetDelegate(WebStateDelegate* delegate) { |
| if (delegate == delegate_) |
| return; |
| if (delegate_) |
| delegate_->Detach(this); |
| delegate_ = delegate; |
| if (delegate_) { |
| delegate_->Attach(this); |
| } |
| } |
| |
| void WebStateImpl::AddObserver(WebStateObserver* observer) { |
| DCHECK(!observers_.HasObserver(observer)); |
| observers_.AddObserver(observer); |
| } |
| |
| void WebStateImpl::RemoveObserver(WebStateObserver* observer) { |
| DCHECK(observers_.HasObserver(observer)); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void WebStateImpl::AddPolicyDecider(WebStatePolicyDecider* decider) { |
| // Despite the name, ObserverList is actually generic, so it is used for |
| // deciders. This makes the call here odd looking, but it's really just |
| // managing the list, not setting observers on deciders. |
| DCHECK(!policy_deciders_.HasObserver(decider)); |
| policy_deciders_.AddObserver(decider); |
| } |
| |
| void WebStateImpl::RemovePolicyDecider(WebStatePolicyDecider* decider) { |
| // Despite the name, ObserverList is actually generic, so it is used for |
| // deciders. This makes the call here odd looking, but it's really just |
| // managing the list, not setting observers on deciders. |
| DCHECK(policy_deciders_.HasObserver(decider)); |
| policy_deciders_.RemoveObserver(decider); |
| } |
| |
| bool WebStateImpl::Configured() const { |
| return web_controller_ != nil; |
| } |
| |
| CRWWebController* WebStateImpl::GetWebController() { |
| return web_controller_; |
| } |
| |
| void WebStateImpl::SetWebController(CRWWebController* web_controller) { |
| [web_controller_ close]; |
| web_controller_ = web_controller; |
| } |
| |
| void WebStateImpl::OnBackForwardStateChanged() { |
| for (auto& observer : observers_) |
| observer.DidChangeBackForwardState(this); |
| } |
| |
| void WebStateImpl::OnTitleChanged() { |
| for (auto& observer : observers_) |
| observer.TitleWasSet(this); |
| } |
| |
| void WebStateImpl::OnRenderProcessGone() { |
| for (auto& observer : observers_) |
| observer.RenderProcessGone(this); |
| } |
| |
| void WebStateImpl::OnScriptCommandReceived(const std::string& command, |
| const base::DictionaryValue& value, |
| const GURL& page_url, |
| bool user_is_interacting, |
| web::WebFrame* sender_frame) { |
| size_t dot_position = command.find_first_of('.'); |
| if (dot_position == 0 || dot_position == std::string::npos) |
| return; |
| |
| std::string prefix = command.substr(0, dot_position); |
| auto it = script_command_callbacks_.find(prefix); |
| if (it == script_command_callbacks_.end()) |
| return; |
| |
| it->second.Notify(value, page_url, user_is_interacting, sender_frame); |
| } |
| |
| void WebStateImpl::SetIsLoading(bool is_loading) { |
| if (is_loading == is_loading_) |
| return; |
| |
| is_loading_ = is_loading; |
| |
| if (is_loading) { |
| for (auto& observer : observers_) |
| observer.DidStartLoading(this); |
| } else { |
| for (auto& observer : observers_) |
| observer.DidStopLoading(this); |
| } |
| } |
| |
| bool WebStateImpl::IsLoading() const { |
| return is_loading_; |
| } |
| |
| double WebStateImpl::GetLoadingProgress() const { |
| if (navigation_manager_->IsRestoreSessionInProgress()) |
| return 0.0; |
| |
| return [web_controller_ loadingProgress]; |
| } |
| |
| bool WebStateImpl::IsCrashed() const { |
| return [web_controller_ isWebProcessCrashed]; |
| } |
| |
| bool WebStateImpl::IsVisible() const { |
| return [web_controller_ isVisible]; |
| } |
| |
| bool WebStateImpl::IsEvicted() const { |
| return ![web_controller_ isViewAlive]; |
| } |
| |
| bool WebStateImpl::IsBeingDestroyed() const { |
| return is_being_destroyed_; |
| } |
| |
| void WebStateImpl::OnPageLoaded(const GURL& url, bool load_success) { |
| // Navigation manager loads internal URLs to restore session history and |
| // create back-forward entries for WebUI. Do not trigger external callbacks. |
| if (wk_navigation_util::IsWKInternalUrl(url)) |
| return; |
| |
| PageLoadCompletionStatus load_completion_status = |
| load_success ? PageLoadCompletionStatus::SUCCESS |
| : PageLoadCompletionStatus::FAILURE; |
| for (auto& observer : observers_) |
| observer.PageLoaded(this, load_completion_status); |
| } |
| |
| void WebStateImpl::OnFaviconUrlUpdated( |
| const std::vector<FaviconURL>& candidates) { |
| cached_favicon_urls_ = candidates; |
| for (auto& observer : observers_) |
| observer.FaviconUrlUpdated(this, candidates); |
| } |
| |
| const NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() const { |
| return *navigation_manager_; |
| } |
| |
| NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() { |
| return *navigation_manager_; |
| } |
| |
| const WebFramesManagerImpl& WebStateImpl::GetWebFramesManagerImpl() const { |
| return web_frames_manager_; |
| } |
| |
| WebFramesManagerImpl& WebStateImpl::GetWebFramesManagerImpl() { |
| return web_frames_manager_; |
| } |
| |
| const SessionCertificatePolicyCacheImpl& |
| WebStateImpl::GetSessionCertificatePolicyCacheImpl() const { |
| return *certificate_policy_cache_; |
| } |
| |
| SessionCertificatePolicyCacheImpl& |
| WebStateImpl::GetSessionCertificatePolicyCacheImpl() { |
| return *certificate_policy_cache_; |
| } |
| |
| void WebStateImpl::CreateWebUI(const GURL& url) { |
| if (HasWebUI()) { |
| if (web_ui_->GetController()->GetHost() == url.host()) { |
| // Don't recreate webUI for the same host. |
| return; |
| } |
| ClearWebUI(); |
| } |
| web_ui_ = CreateWebUIIOS(url); |
| } |
| |
| void WebStateImpl::ClearWebUI() { |
| web_ui_.reset(); |
| } |
| |
| bool WebStateImpl::HasWebUI() { |
| return !!web_ui_; |
| } |
| |
| const std::u16string& WebStateImpl::GetTitle() const { |
| // TODO(stuartmorgan): Implement the NavigationManager logic necessary to |
| // match the WebContents implementation of this method. |
| DCHECK(Configured()); |
| web::NavigationItem* item = navigation_manager_->GetLastCommittedItem(); |
| // Display title for the visible item makes more sense. |
| item = navigation_manager_->GetVisibleItem(); |
| return item ? item->GetTitleForDisplay() : empty_string16_; |
| } |
| |
| void WebStateImpl::SendChangeLoadProgress(double progress) { |
| for (auto& observer : observers_) |
| observer.LoadProgressChanged(this, progress); |
| } |
| |
| void WebStateImpl::HandleContextMenu(const web::ContextMenuParams& params) { |
| if (delegate_) { |
| delegate_->HandleContextMenu(this, params); |
| } |
| } |
| |
| void WebStateImpl::ShowRepostFormWarningDialog( |
| base::OnceCallback<void(bool)> callback) { |
| if (delegate_) { |
| delegate_->ShowRepostFormWarningDialog(this, std::move(callback)); |
| } else { |
| std::move(callback).Run(true); |
| } |
| } |
| |
| void WebStateImpl::RunJavaScriptDialog( |
| const GURL& origin_url, |
| JavaScriptDialogType javascript_dialog_type, |
| NSString* message_text, |
| NSString* default_prompt_text, |
| DialogClosedCallback callback) { |
| JavaScriptDialogPresenter* presenter = |
| delegate_ ? delegate_->GetJavaScriptDialogPresenter(this) : nullptr; |
| if (!presenter) { |
| std::move(callback).Run(false, nil); |
| return; |
| } |
| running_javascript_dialog_ = true; |
| DialogClosedCallback presenter_callback = |
| base::BindOnce(&WebStateImpl::JavaScriptDialogClosed, |
| weak_factory_.GetWeakPtr(), std::move(callback)); |
| presenter->RunJavaScriptDialog(this, origin_url, javascript_dialog_type, |
| message_text, default_prompt_text, |
| std::move(presenter_callback)); |
| } |
| |
| void WebStateImpl::JavaScriptDialogClosed( |
| base::WeakPtr<WebStateImpl> weak_web_state, |
| DialogClosedCallback callback, |
| bool success, |
| NSString* user_input) { |
| if (weak_web_state) { |
| weak_web_state->running_javascript_dialog_ = false; |
| } |
| std::move(callback).Run(success, user_input); |
| } |
| |
| WebState* WebStateImpl::CreateNewWebState(const GURL& url, |
| const GURL& opener_url, |
| bool initiated_by_user) { |
| if (delegate_) { |
| return delegate_->CreateNewWebState(this, url, opener_url, |
| initiated_by_user); |
| } |
| return nullptr; |
| } |
| |
| void WebStateImpl::CloseWebState() { |
| if (delegate_) { |
| delegate_->CloseWebState(this); |
| } |
| } |
| |
| UserAgentType WebStateImpl::GetUserAgentForNextNavigation(const GURL& url) { |
| if (user_agent_type_ == UserAgentType::AUTOMATIC) { |
| UIView* container = |
| GetWebViewContainer() ? GetWebViewContainer() : GetView(); |
| return GetWebClient()->GetDefaultUserAgent(container, url); |
| } |
| return user_agent_type_; |
| } |
| |
| UserAgentType WebStateImpl::GetUserAgentForSessionRestoration() const { |
| return user_agent_type_; |
| } |
| |
| void WebStateImpl::SetUserAgent(UserAgentType user_agent) { |
| user_agent_type_ = user_agent; |
| } |
| |
| void WebStateImpl::OnAuthRequired(NSURLProtectionSpace* protection_space, |
| NSURLCredential* proposed_credential, |
| WebStateDelegate::AuthCallback callback) { |
| if (delegate_) { |
| delegate_->OnAuthRequired(this, protection_space, proposed_credential, |
| std::move(callback)); |
| } else { |
| std::move(callback).Run(nil, nil); |
| } |
| } |
| |
| void WebStateImpl::CancelDialogs() { |
| if (delegate_) { |
| JavaScriptDialogPresenter* presenter = |
| delegate_->GetJavaScriptDialogPresenter(this); |
| if (presenter) { |
| presenter->CancelDialogs(this); |
| } |
| } |
| } |
| |
| std::unique_ptr<web::WebUIIOS> WebStateImpl::CreateWebUIIOS(const GURL& url) { |
| WebUIIOSControllerFactory* factory = |
| WebUIIOSControllerFactoryRegistry::GetInstance(); |
| if (!factory) |
| return nullptr; |
| std::unique_ptr<web::WebUIIOS> web_ui = std::make_unique<WebUIIOSImpl>(this); |
| auto controller = factory->CreateWebUIIOSControllerForURL(web_ui.get(), url); |
| if (!controller) |
| return nullptr; |
| |
| web_ui->SetController(std::move(controller)); |
| return web_ui; |
| } |
| |
| void WebStateImpl::SetContentsMimeType(const std::string& mime_type) { |
| mime_type_ = mime_type; |
| } |
| |
| WebStatePolicyDecider::PolicyDecision WebStateImpl::ShouldAllowRequest( |
| NSURLRequest* request, |
| const WebStatePolicyDecider::RequestInfo& request_info) { |
| for (auto& policy_decider : policy_deciders_) { |
| WebStatePolicyDecider::PolicyDecision result = |
| policy_decider.ShouldAllowRequest(request, request_info); |
| if (result.ShouldCancelNavigation()) { |
| return result; |
| } |
| } |
| return WebStatePolicyDecider::PolicyDecision::Allow(); |
| } |
| |
| void WebStateImpl::ShouldAllowResponse( |
| NSURLResponse* response, |
| bool for_main_frame, |
| base::OnceCallback<void(WebStatePolicyDecider::PolicyDecision)> callback) { |
| auto response_state_tracker = |
| std::make_unique<PolicyDecisionStateTracker>(std::move(callback)); |
| PolicyDecisionStateTracker* response_state_tracker_ptr = |
| response_state_tracker.get(); |
| auto policy_decider_callback = base::BindRepeating( |
| &PolicyDecisionStateTracker::OnSinglePolicyDecisionReceived, |
| base::Owned(std::move(response_state_tracker))); |
| int num_decisions_requested = 0; |
| for (auto& policy_decider : policy_deciders_) { |
| policy_decider.ShouldAllowResponse(response, for_main_frame, |
| policy_decider_callback); |
| num_decisions_requested++; |
| if (response_state_tracker_ptr->DeterminedFinalResult()) |
| break; |
| } |
| |
| response_state_tracker_ptr->FinishedRequestingDecisions( |
| num_decisions_requested); |
| } |
| |
| bool WebStateImpl::ShouldPreviewLink(const GURL& link_url) { |
| return delegate_ && delegate_->ShouldPreviewLink(this, link_url); |
| } |
| |
| UIViewController* WebStateImpl::GetPreviewingViewController( |
| const GURL& link_url) { |
| return delegate_ ? delegate_->GetPreviewingViewController(this, link_url) |
| : nil; |
| } |
| |
| void WebStateImpl::CommitPreviewingViewController( |
| UIViewController* previewing_view_controller) { |
| if (delegate_) { |
| delegate_->CommitPreviewingViewController(this, previewing_view_controller); |
| } |
| } |
| |
| UIView* WebStateImpl::GetWebViewContainer() { |
| if (delegate_) { |
| return delegate_->GetWebViewContainer(this); |
| } |
| return nil; |
| } |
| |
| #pragma mark - RequestTracker management |
| |
| void WebStateImpl::DidChangeVisibleSecurityState() { |
| for (auto& observer : observers_) |
| observer.DidChangeVisibleSecurityState(this); |
| } |
| |
| WebState::InterfaceBinder* WebStateImpl::GetInterfaceBinderForMainFrame() { |
| return &interface_binder_; |
| } |
| |
| #pragma mark - WebFrame management |
| |
| void WebStateImpl::OnWebFrameAvailable(web::WebFrame* frame) { |
| for (auto& observer : observers_) |
| observer.WebFrameDidBecomeAvailable(this, frame); |
| } |
| |
| void WebStateImpl::OnWebFrameUnavailable(web::WebFrame* frame) { |
| for (auto& observer : observers_) |
| observer.WebFrameWillBecomeUnavailable(this, frame); |
| } |
| |
| #pragma mark - WebState implementation |
| |
| bool WebStateImpl::IsWebUsageEnabled() const { |
| return [web_controller_ webUsageEnabled]; |
| } |
| |
| void WebStateImpl::SetWebUsageEnabled(bool enabled) { |
| [web_controller_ setWebUsageEnabled:enabled]; |
| } |
| |
| UIView* WebStateImpl::GetView() { |
| return [web_controller_ view]; |
| } |
| |
| void WebStateImpl::DidCoverWebContent() { |
| [web_controller_ removeWebViewFromViewHierarchy]; |
| WasHidden(); |
| } |
| |
| void WebStateImpl::DidRevealWebContent() { |
| [web_controller_ addWebViewToViewHierarchy]; |
| WasShown(); |
| } |
| |
| void WebStateImpl::WasShown() { |
| if (IsVisible()) |
| return; |
| |
| [web_controller_ wasShown]; |
| for (auto& observer : observers_) |
| observer.WasShown(this); |
| } |
| |
| void WebStateImpl::WasHidden() { |
| if (!IsVisible()) |
| return; |
| |
| [web_controller_ wasHidden]; |
| for (auto& observer : observers_) |
| observer.WasHidden(this); |
| } |
| |
| void WebStateImpl::SetKeepRenderProcessAlive(bool keep_alive) { |
| [web_controller_ setKeepsRenderProcessAlive:keep_alive]; |
| } |
| |
| BrowserState* WebStateImpl::GetBrowserState() const { |
| return navigation_manager_->GetBrowserState(); |
| } |
| |
| void WebStateImpl::OpenURL(const WebState::OpenURLParams& params) { |
| DCHECK(Configured()); |
| if (delegate_) |
| delegate_->OpenURLFromWebState(this, params); |
| } |
| |
| void WebStateImpl::Stop() { |
| if (navigation_manager_->IsRestoreSessionInProgress()) { |
| // Do not interrupt session restoration process. For embedder session |
| // restoration is opaque and WebState acts like ut's idle. |
| return; |
| } |
| [web_controller_ stopLoading]; |
| } |
| |
| const NavigationManager* WebStateImpl::GetNavigationManager() const { |
| return &GetNavigationManagerImpl(); |
| } |
| |
| NavigationManager* WebStateImpl::GetNavigationManager() { |
| return &GetNavigationManagerImpl(); |
| } |
| |
| const WebFramesManager* WebStateImpl::GetWebFramesManager() const { |
| return &web_frames_manager_; |
| } |
| |
| WebFramesManager* WebStateImpl::GetWebFramesManager() { |
| return &web_frames_manager_; |
| } |
| |
| const SessionCertificatePolicyCache* |
| WebStateImpl::GetSessionCertificatePolicyCache() const { |
| return &GetSessionCertificatePolicyCacheImpl(); |
| } |
| |
| SessionCertificatePolicyCache* |
| WebStateImpl::GetSessionCertificatePolicyCache() { |
| return &GetSessionCertificatePolicyCacheImpl(); |
| } |
| |
| CRWSessionStorage* WebStateImpl::BuildSessionStorage() { |
| [web_controller_ recordStateInHistory]; |
| if (restored_session_storage_) { |
| // UserData can be updated in an uncommitted WebState. Even |
| // if a WebState hasn't been restored, its opener value may have changed. |
| std::unique_ptr<web::SerializableUserData> serializable_user_data = |
| web::SerializableUserDataManager::FromWebState(this) |
| ->CreateSerializableUserData(); |
| [restored_session_storage_ |
| setSerializableUserData:std::move(serializable_user_data)]; |
| return restored_session_storage_; |
| } |
| SessionStorageBuilder session_storage_builder; |
| return session_storage_builder.BuildStorage(this); |
| } |
| |
| void WebStateImpl::LoadData(NSData* data, |
| NSString* mime_type, |
| const GURL& url) { |
| [web_controller_ loadData:data MIMEType:mime_type forURL:url]; |
| } |
| |
| CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const { |
| return [web_controller_.jsInjector JSInjectionReceiver]; |
| } |
| |
| void WebStateImpl::ExecuteJavaScript(const std::u16string& javascript) { |
| [web_controller_.jsInjector |
| executeJavaScript:base::SysUTF16ToNSString(javascript) |
| completionHandler:nil]; |
| } |
| |
| void WebStateImpl::ExecuteJavaScript(const std::u16string& javascript, |
| JavaScriptResultCallback callback) { |
| __block JavaScriptResultCallback stack_callback = std::move(callback); |
| [web_controller_.jsInjector |
| executeJavaScript:base::SysUTF16ToNSString(javascript) |
| completionHandler:^(id value, NSError* error) { |
| if (error) { |
| DLOG(WARNING) << "Script execution failed with error: " |
| << base::SysNSStringToUTF16( |
| error.userInfo[NSLocalizedDescriptionKey]); |
| } |
| std::move(stack_callback).Run(ValueResultFromWKResult(value).get()); |
| }]; |
| } |
| |
| void WebStateImpl::ExecuteUserJavaScript(NSString* javaScript) { |
| [web_controller_.jsInjector executeUserJavaScript:javaScript |
| completionHandler:nil]; |
| } |
| |
| const std::string& WebStateImpl::GetContentsMimeType() const { |
| return mime_type_; |
| } |
| |
| bool WebStateImpl::ContentIsHTML() const { |
| return [web_controller_ contentIsHTML]; |
| } |
| |
| const GURL& WebStateImpl::GetVisibleURL() const { |
| web::NavigationItem* item = navigation_manager_->GetVisibleItem(); |
| return item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| } |
| |
| const GURL& WebStateImpl::GetLastCommittedURL() const { |
| web::NavigationItem* item = navigation_manager_->GetLastCommittedItem(); |
| return item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| } |
| |
| GURL WebStateImpl::GetCurrentURL(URLVerificationTrustLevel* trust_level) const { |
| if (!trust_level) { |
| auto ignore_trust = URLVerificationTrustLevel::kNone; |
| return [web_controller_ currentURLWithTrustLevel:&ignore_trust]; |
| } |
| GURL result = [web_controller_ currentURLWithTrustLevel:trust_level]; |
| |
| web::NavigationItemImpl* item = |
| navigation_manager_->GetLastCommittedItemImpl(); |
| GURL lastCommittedURL; |
| if (item) { |
| if (!base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && |
| (wk_navigation_util::IsPlaceholderUrl(item->GetURL()) || |
| item->error_retry_state_machine().state() == |
| ErrorRetryState::kReadyToDisplayError)) { |
| // When webView.URL is a placeholder URL, |currentURLWithTrustLevel:| |
| // returns virtual URL if one is available. |
| lastCommittedURL = item->GetVirtualURL(); |
| } else { |
| // Otherwise document URL is returned. |
| lastCommittedURL = item->GetURL(); |
| } |
| } |
| |
| bool equalOrigins; |
| if (result.SchemeIs(url::kAboutScheme) && |
| web::GetWebClient()->IsAppSpecificURL(GetLastCommittedURL())) { |
| // This special case is added for any app specific URLs that have been |
| // rewritten to about:// URLs. In this case, an about scheme does not have |
| // an origin to compare, only a path. |
| equalOrigins = result.path() == lastCommittedURL.path(); |
| } else { |
| equalOrigins = result.GetOrigin() == lastCommittedURL.GetOrigin(); |
| } |
| UMA_HISTOGRAM_BOOLEAN("Web.CurrentOriginEqualsLastCommittedOrigin", |
| equalOrigins); |
| if (!equalOrigins || (item && item->IsUntrusted())) { |
| *trust_level = web::URLVerificationTrustLevel::kMixed; |
| } |
| return result; |
| } |
| |
| base::CallbackListSubscription WebStateImpl::AddScriptCommandCallback( |
| const ScriptCommandCallback& callback, |
| const std::string& command_prefix) { |
| DCHECK(!command_prefix.empty()); |
| DCHECK(command_prefix.find_first_of('.') == std::string::npos); |
| DCHECK(script_command_callbacks_.count(command_prefix) == 0 || |
| script_command_callbacks_[command_prefix].empty()); |
| return script_command_callbacks_[command_prefix].Add(callback); |
| } |
| |
| id<CRWWebViewProxy> WebStateImpl::GetWebViewProxy() const { |
| return [web_controller_ webViewProxy]; |
| } |
| |
| bool WebStateImpl::HasOpener() const { |
| return created_with_opener_; |
| } |
| |
| void WebStateImpl::SetHasOpener(bool has_opener) { |
| created_with_opener_ = has_opener; |
| } |
| |
| bool WebStateImpl::CanTakeSnapshot() const { |
| // The WKWebView snapshot API depends on IPC execution that does not function |
| // properly when JavaScript dialogs are running. |
| return !running_javascript_dialog_; |
| } |
| |
| void WebStateImpl::TakeSnapshot(const gfx::RectF& rect, |
| SnapshotCallback callback) { |
| DCHECK(CanTakeSnapshot()); |
| // Move the callback to a __block pointer, which will be in scope as long |
| // as the callback is retained. |
| __block SnapshotCallback shared_callback = std::move(callback); |
| [web_controller_ takeSnapshotWithRect:rect.ToCGRect() |
| completion:^(UIImage* snapshot) { |
| shared_callback.Run(gfx::Image(snapshot)); |
| }]; |
| } |
| |
| void WebStateImpl::CreateFullPagePdf( |
| base::OnceCallback<void(NSData*)> callback) { |
| // Move the callback to a __block pointer, which will be in scope as long |
| // as the callback is retained. |
| __block base::OnceCallback<void(NSData*)> callback_for_block = |
| std::move(callback); |
| [web_controller_ |
| createFullPagePDFWithCompletion:^(NSData* pdf_document_data) { |
| std::move(callback_for_block).Run(pdf_document_data); |
| }]; |
| } |
| |
| void WebStateImpl::OnNavigationStarted(web::NavigationContextImpl* context) { |
| // Navigation manager loads internal URLs to restore session history and |
| // create back-forward entries for WebUI. Do not trigger external callbacks. |
| if ((!base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && |
| context->IsPlaceholderNavigation()) || |
| (base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && |
| [CRWErrorPageHelper isErrorPageFileURL:context->GetUrl()]) || |
| wk_navigation_util::IsRestoreSessionUrl(context->GetUrl())) { |
| return; |
| } |
| |
| for (auto& observer : observers_) |
| observer.DidStartNavigation(this, context); |
| } |
| |
| void WebStateImpl::OnNavigationRedirected(web::NavigationContextImpl* context) { |
| for (auto& observer : observers_) |
| observer.DidRedirectNavigation(this, context); |
| } |
| |
| void WebStateImpl::OnNavigationFinished(web::NavigationContextImpl* context) { |
| // Navigation manager loads internal URLs to restore session history and |
| // create back-forward entries for WebUI. Do not trigger external callbacks. |
| if ((!base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && |
| context->IsPlaceholderNavigation()) || |
| (base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && |
| [CRWErrorPageHelper isErrorPageFileURL:context->GetUrl()]) || |
| wk_navigation_util::IsRestoreSessionUrl(context->GetUrl())) { |
| return; |
| } |
| |
| for (auto& observer : observers_) |
| observer.DidFinishNavigation(this, context); |
| |
| // Update cached_favicon_urls_. |
| if (!context->IsSameDocument()) { |
| // Favicons are not valid after document change. Favicon URLs will be |
| // refetched by CRWWebController and passed to OnFaviconUrlUpdated. |
| cached_favicon_urls_.clear(); |
| } else if (!cached_favicon_urls_.empty()) { |
| // For same-document navigations favicon urls will not be refetched and |
| // WebStateObserver:FaviconUrlUpdated must use the cached results. |
| for (auto& observer : observers_) { |
| observer.FaviconUrlUpdated(this, cached_favicon_urls_); |
| } |
| } |
| } |
| |
| #pragma mark - NavigationManagerDelegate implementation |
| |
| void WebStateImpl::ClearDialogs() { |
| CancelDialogs(); |
| } |
| |
| void WebStateImpl::RecordPageStateInNavigationItem() { |
| [web_controller_ recordStateInHistory]; |
| } |
| |
| void WebStateImpl::LoadCurrentItem(NavigationInitiationType type) { |
| [web_controller_ loadCurrentURLWithRendererInitiatedNavigation: |
| type == NavigationInitiationType::RENDERER_INITIATED]; |
| } |
| |
| void WebStateImpl::LoadIfNecessary() { |
| [web_controller_ loadCurrentURLIfNecessary]; |
| } |
| |
| void WebStateImpl::Reload() { |
| [web_controller_ reloadWithRendererInitiatedNavigation:NO]; |
| } |
| |
| void WebStateImpl::OnNavigationItemCommitted(NavigationItem* item) { |
| if (wk_navigation_util::IsWKInternalUrl(item->GetURL())) |
| return; |
| |
| // A committed navigation item indicates that NavigationManager has a new |
| // valid session history so should invalidate the cached restored session |
| // history. |
| restored_session_storage_ = nil; |
| } |
| |
| WebState* WebStateImpl::GetWebState() { |
| return this; |
| } |
| |
| void WebStateImpl::SetWebStateUserAgent(UserAgentType user_agent_type) { |
| SetUserAgent(user_agent_type); |
| } |
| |
| id<CRWWebViewNavigationProxy> WebStateImpl::GetWebViewNavigationProxy() const { |
| return [web_controller_ webViewNavigationProxy]; |
| } |
| |
| void WebStateImpl::GoToBackForwardListItem(WKBackForwardListItem* wk_item, |
| NavigationItem* item, |
| NavigationInitiationType type, |
| bool has_user_gesture) { |
| return [web_controller_ goToBackForwardListItem:wk_item |
| navigationItem:item |
| navigationInitiationType:type |
| hasUserGesture:has_user_gesture]; |
| } |
| |
| void WebStateImpl::RemoveWebView() { |
| return [web_controller_ removeWebView]; |
| } |
| |
| NavigationItemImpl* WebStateImpl::GetPendingItem() { |
| return [web_controller_ lastPendingItemForNewNavigation]; |
| } |
| |
| void WebStateImpl::RestoreSessionStorage(CRWSessionStorage* session_storage) { |
| // Session storage restore is asynchronous because it involves a page load in |
| // WKWebView. Temporarily cache the restored session so it can be returned if |
| // BuildSessionStorage() or GetTitle() is called before the actual restoration |
| // completes. This can happen to inactive tabs when a navigation in the |
| // current tab triggers the serialization of all tabs and when user clicks on |
| // tab switcher without switching to a tab. |
| restored_session_storage_ = session_storage; |
| SessionStorageBuilder session_storage_builder; |
| session_storage_builder.ExtractSessionState(this, session_storage); |
| } |
| |
| } // namespace web |