| // Copyright 2019 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. |
| |
| #include "weblayer/browser/navigation_controller_impl.h" |
| |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/containers/contains.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/navigation_throttle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "third_party/blink/public/mojom/navigation/was_activated_option.mojom-shared.h" |
| #include "ui/base/page_transition_types.h" |
| #include "weblayer/browser/navigation_entry_data.h" |
| #include "weblayer/browser/navigation_ui_data_impl.h" |
| #include "weblayer/browser/page_impl.h" |
| #include "weblayer/browser/tab_impl.h" |
| #include "weblayer/public/navigation_observer.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "base/android/jni_string.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/embedder_support/android/util/web_resource_response.h" |
| #include "weblayer/browser/java/jni/NavigationControllerImpl_jni.h" |
| #endif |
| |
| #if BUILDFLAG(IS_ANDROID) |
| using base::android::AttachCurrentThread; |
| using base::android::JavaParamRef; |
| using base::android::ScopedJavaLocalRef; |
| #endif |
| |
| namespace weblayer { |
| |
| class NavigationControllerImpl::DelayDeletionHelper { |
| public: |
| explicit DelayDeletionHelper(NavigationControllerImpl* controller) |
| : controller_(controller->weak_ptr_factory_.GetWeakPtr()) { |
| // This should never be called reentrantly. |
| DCHECK(!controller->should_delay_web_contents_deletion_); |
| controller->should_delay_web_contents_deletion_ = true; |
| } |
| |
| DelayDeletionHelper(const DelayDeletionHelper&) = delete; |
| DelayDeletionHelper& operator=(const DelayDeletionHelper&) = delete; |
| |
| ~DelayDeletionHelper() { |
| if (controller_) |
| controller_->should_delay_web_contents_deletion_ = false; |
| } |
| |
| bool WasControllerDeleted() { return controller_.get() == nullptr; } |
| |
| private: |
| base::WeakPtr<NavigationControllerImpl> controller_; |
| }; |
| |
| // NavigationThrottle implementation responsible for delaying certain |
| // operations and performing them when safe. This is necessary as content |
| // does allow certain operations to be called at certain times. For example, |
| // content does not allow calling WebContents::Stop() from |
| // WebContentsObserver::DidStartNavigation() (to do so crashes). To work around |
| // this NavigationControllerImpl detects these scenarios and delays processing |
| // until safe. |
| // |
| // Most of the support for these scenarios is handled by a custom |
| // NavigationThrottle. To make things interesting, the NavigationThrottle is |
| // created after some of the scenarios this code wants to handle. As such, |
| // NavigationImpl does some amount of caching until the NavigationThrottle is |
| // created. |
| class NavigationControllerImpl::NavigationThrottleImpl |
| : public content::NavigationThrottle { |
| public: |
| NavigationThrottleImpl(NavigationControllerImpl* controller, |
| content::NavigationHandle* handle) |
| : NavigationThrottle(handle), controller_(controller) {} |
| NavigationThrottleImpl(const NavigationThrottleImpl&) = delete; |
| NavigationThrottleImpl& operator=(const NavigationThrottleImpl&) = delete; |
| ~NavigationThrottleImpl() override = default; |
| |
| void ScheduleCancel() { should_cancel_ = true; } |
| |
| // content::NavigationThrottle: |
| ThrottleCheckResult WillStartRequest() override { |
| return should_cancel_ ? CANCEL : PROCEED; |
| } |
| |
| ThrottleCheckResult WillRedirectRequest() override { |
| controller_->WillRedirectRequest(this, navigation_handle()); |
| return should_cancel_ ? CANCEL : PROCEED; |
| } |
| |
| const char* GetNameForLogging() override { |
| return "WebLayerNavigationControllerThrottle"; |
| } |
| |
| private: |
| raw_ptr<NavigationControllerImpl> controller_; |
| bool should_cancel_ = false; |
| }; |
| |
| NavigationControllerImpl::NavigationControllerImpl(TabImpl* tab) |
| : WebContentsObserver(tab->web_contents()) {} |
| |
| NavigationControllerImpl::~NavigationControllerImpl() = default; |
| |
| std::unique_ptr<content::NavigationThrottle> |
| NavigationControllerImpl::CreateNavigationThrottle( |
| content::NavigationHandle* handle) { |
| if (!handle->IsInMainFrame()) |
| return nullptr; |
| |
| auto throttle = std::make_unique<NavigationThrottleImpl>(this, handle); |
| DCHECK(navigation_map_.find(handle) != navigation_map_.end()); |
| auto* navigation = navigation_map_[handle].get(); |
| if (navigation->should_stop_when_throttle_created()) |
| throttle->ScheduleCancel(); |
| return throttle; |
| } |
| |
| NavigationImpl* NavigationControllerImpl::GetNavigationImplFromHandle( |
| content::NavigationHandle* handle) { |
| auto iter = navigation_map_.find(handle); |
| return iter == navigation_map_.end() ? nullptr : iter->second.get(); |
| } |
| |
| NavigationImpl* NavigationControllerImpl::GetNavigationImplFromId( |
| int64_t navigation_id) { |
| for (const auto& iter : navigation_map_) { |
| if (iter.first->GetNavigationId() == navigation_id) |
| return iter.second.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| void NavigationControllerImpl::OnFirstContentfulPaint( |
| const base::TimeTicks& navigation_start, |
| const base::TimeDelta& first_contentful_paint) { |
| #if BUILDFLAG(IS_ANDROID) |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_onFirstContentfulPaint2"); |
| int64_t first_contentful_paint_ms = first_contentful_paint.InMilliseconds(); |
| Java_NavigationControllerImpl_onFirstContentfulPaint2( |
| AttachCurrentThread(), java_controller_, |
| navigation_start.ToUptimeMillis(), first_contentful_paint_ms); |
| #endif |
| |
| for (auto& observer : observers_) |
| observer.OnFirstContentfulPaint(navigation_start, first_contentful_paint); |
| } |
| |
| void NavigationControllerImpl::OnLargestContentfulPaint( |
| const base::TimeTicks& navigation_start, |
| const base::TimeDelta& largest_contentful_paint) { |
| #if BUILDFLAG(IS_ANDROID) |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_onLargestContentfulPaint2"); |
| int64_t largest_contentful_paint_ms = |
| largest_contentful_paint.InMilliseconds(); |
| Java_NavigationControllerImpl_onLargestContentfulPaint( |
| AttachCurrentThread(), java_controller_, |
| navigation_start.ToUptimeMillis(), largest_contentful_paint_ms); |
| #endif |
| |
| for (auto& observer : observers_) |
| observer.OnLargestContentfulPaint(navigation_start, |
| largest_contentful_paint); |
| } |
| |
| void NavigationControllerImpl::OnPageDestroyed(Page* page) { |
| for (auto& observer : observers_) |
| observer.OnPageDestroyed(page); |
| } |
| |
| void NavigationControllerImpl::OnPageLanguageDetermined( |
| Page* page, |
| const std::string& language) { |
| #if BUILDFLAG(IS_ANDROID) |
| JNIEnv* env = AttachCurrentThread(); |
| Java_NavigationControllerImpl_onPageLanguageDetermined( |
| env, java_controller_, static_cast<PageImpl*>(page)->java_page(), |
| base::android::ConvertUTF8ToJavaString(env, language)); |
| #endif |
| |
| for (auto& observer : observers_) |
| observer.OnPageLanguageDetermined(page, language); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| void NavigationControllerImpl::SetNavigationControllerImpl( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& java_controller) { |
| java_controller_ = java_controller; |
| } |
| |
| void NavigationControllerImpl::Navigate( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& url, |
| jboolean should_replace_current_entry, |
| jboolean disable_intent_processing, |
| jboolean allow_intent_launches_in_background, |
| jboolean disable_network_error_auto_reload, |
| jboolean enable_auto_play, |
| const base::android::JavaParamRef<jobject>& response) { |
| auto params = std::make_unique<content::NavigationController::LoadURLParams>( |
| GURL(base::android::ConvertJavaStringToUTF8(env, url))); |
| params->should_replace_current_entry = should_replace_current_entry; |
| // On android, the transition type largely dictates whether intent processing |
| // happens. PAGE_TRANSITION_TYPED does not process intents, where as |
| // PAGE_TRANSITION_LINK will (with the caveat that even links may not trigger |
| // intent processing under some circumstances). |
| params->transition_type = disable_intent_processing |
| ? ui::PAGE_TRANSITION_TYPED |
| : ui::PAGE_TRANSITION_LINK; |
| auto data = std::make_unique<NavigationUIDataImpl>(); |
| |
| if (disable_network_error_auto_reload) |
| data->set_disable_network_error_auto_reload(true); |
| |
| data->set_allow_intent_launches_in_background( |
| allow_intent_launches_in_background); |
| |
| if (!response.is_null()) { |
| data->SetResponse( |
| std::make_unique<embedder_support::WebResourceResponse>(response)); |
| } |
| |
| params->navigation_ui_data = std::move(data); |
| |
| if (enable_auto_play) |
| params->was_activated = blink::mojom::WasActivatedOption::kYes; |
| |
| DoNavigate(std::move(params)); |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| NavigationControllerImpl::GetNavigationEntryDisplayUri(JNIEnv* env, int index) { |
| return ScopedJavaLocalRef<jstring>(base::android::ConvertUTF8ToJavaString( |
| env, GetNavigationEntryDisplayURL(index).spec())); |
| } |
| |
| ScopedJavaLocalRef<jstring> NavigationControllerImpl::GetNavigationEntryTitle( |
| JNIEnv* env, |
| int index) { |
| return ScopedJavaLocalRef<jstring>(base::android::ConvertUTF8ToJavaString( |
| env, GetNavigationEntryTitle(index))); |
| } |
| |
| bool NavigationControllerImpl::IsNavigationEntrySkippable(JNIEnv* env, |
| int index) { |
| return IsNavigationEntrySkippable(index); |
| } |
| |
| base::android::ScopedJavaGlobalRef<jobject> |
| NavigationControllerImpl::GetNavigationImplFromId(JNIEnv* env, int64_t id) { |
| auto* navigation_impl = GetNavigationImplFromId(id); |
| return navigation_impl ? navigation_impl->java_navigation() : nullptr; |
| } |
| |
| #endif |
| |
| void NavigationControllerImpl::WillRedirectRequest( |
| NavigationThrottleImpl* throttle, |
| content::NavigationHandle* navigation_handle) { |
| DCHECK(navigation_handle->IsInMainFrame()); |
| DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end()); |
| auto* navigation = navigation_map_[navigation_handle].get(); |
| navigation->set_safe_to_set_request_headers(true); |
| DCHECK(!active_throttle_); |
| base::AutoReset<NavigationThrottleImpl*> auto_reset(&active_throttle_, |
| throttle); |
| #if BUILDFLAG(IS_ANDROID) |
| if (java_controller_) { |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_navigationRedirected"); |
| Java_NavigationControllerImpl_navigationRedirected( |
| AttachCurrentThread(), java_controller_, navigation->java_navigation()); |
| } |
| #endif |
| for (auto& observer : observers_) |
| observer.NavigationRedirected(navigation); |
| navigation->set_safe_to_set_request_headers(false); |
| } |
| |
| void NavigationControllerImpl::AddObserver(NavigationObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void NavigationControllerImpl::RemoveObserver(NavigationObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void NavigationControllerImpl::Navigate(const GURL& url) { |
| DoNavigate( |
| std::make_unique<content::NavigationController::LoadURLParams>(url)); |
| } |
| |
| void NavigationControllerImpl::Navigate( |
| const GURL& url, |
| const NavigationController::NavigateParams& params) { |
| auto load_params = |
| std::make_unique<content::NavigationController::LoadURLParams>(url); |
| load_params->should_replace_current_entry = |
| params.should_replace_current_entry; |
| if (params.enable_auto_play) |
| load_params->was_activated = blink::mojom::WasActivatedOption::kYes; |
| |
| DoNavigate(std::move(load_params)); |
| } |
| |
| void NavigationControllerImpl::GoBack() { |
| web_contents()->GetController().GoBack(); |
| } |
| |
| void NavigationControllerImpl::GoForward() { |
| web_contents()->GetController().GoForward(); |
| } |
| |
| bool NavigationControllerImpl::CanGoBack() { |
| return web_contents()->GetController().CanGoBack(); |
| } |
| |
| bool NavigationControllerImpl::CanGoForward() { |
| return web_contents()->GetController().CanGoForward(); |
| } |
| |
| void NavigationControllerImpl::GoToIndex(int index) { |
| web_contents()->GetController().GoToIndex(index); |
| } |
| |
| void NavigationControllerImpl::Reload() { |
| web_contents()->GetController().Reload(content::ReloadType::NORMAL, true); |
| } |
| |
| void NavigationControllerImpl::Stop() { |
| CancelDelayedLoad(); |
| |
| NavigationImpl* navigation = nullptr; |
| if (navigation_starting_) { |
| navigation_starting_->set_should_stop_when_throttle_created(); |
| navigation = navigation_starting_; |
| } else if (active_throttle_) { |
| active_throttle_->ScheduleCancel(); |
| DCHECK(navigation_map_.find(active_throttle_->navigation_handle()) != |
| navigation_map_.end()); |
| navigation = navigation_map_[active_throttle_->navigation_handle()].get(); |
| } else { |
| web_contents()->Stop(); |
| } |
| |
| if (navigation) |
| navigation->set_was_stopped(); |
| } |
| |
| int NavigationControllerImpl::GetNavigationListSize() { |
| content::NavigationEntry* current_entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| if (current_entry && current_entry->IsInitialEntry()) { |
| // If we're currently on the initial NavigationEntry, no navigation has |
| // committed, so the initial NavigationEntry should not be part of the |
| // "Navigation List", and we should return 0 as the navigation list size. |
| // This also preserves the old behavior where we used to not have the |
| // initial NavigationEntry. |
| return 0; |
| } |
| |
| return web_contents()->GetController().GetEntryCount(); |
| } |
| |
| int NavigationControllerImpl::GetNavigationListCurrentIndex() { |
| content::NavigationEntry* current_entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| if (current_entry && current_entry->IsInitialEntry()) { |
| // If we're currently on the initial NavigationEntry, no navigation has |
| // committed, so the initial NavigationEntry should not be part of the |
| // "Navigation List", and we should return -1 as the current index. This |
| // also preserves the old behavior where we used to not have the initial |
| // NavigationEntry. |
| return -1; |
| } |
| |
| return web_contents()->GetController().GetCurrentEntryIndex(); |
| } |
| |
| GURL NavigationControllerImpl::GetNavigationEntryDisplayURL(int index) { |
| auto* entry = web_contents()->GetController().GetEntryAtIndex(index); |
| // This function should never be called when GetNavigationListSize() is 0 |
| // because `index` should be between 0 and GetNavigationListSize() - 1, which |
| // also means `entry` must not be the initial NavigationEntry. |
| DCHECK_NE(0, GetNavigationListSize()); |
| DCHECK(!entry->IsInitialEntry()); |
| return entry->GetVirtualURL(); |
| } |
| |
| std::string NavigationControllerImpl::GetNavigationEntryTitle(int index) { |
| auto* entry = web_contents()->GetController().GetEntryAtIndex(index); |
| // This function should never be called when GetNavigationListSize() is 0 |
| // because `index` should be between 0 and GetNavigationListSize() - 1, which |
| // also means `entry` must not be the initial NavigationEntry. |
| DCHECK_NE(0, GetNavigationListSize()); |
| DCHECK(!entry->IsInitialEntry()); |
| return base::UTF16ToUTF8(entry->GetTitle()); |
| } |
| |
| bool NavigationControllerImpl::IsNavigationEntrySkippable(int index) { |
| return web_contents()->GetController().IsEntryMarkedToBeSkipped(index); |
| } |
| |
| void NavigationControllerImpl::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| // TODO(https://crbug.com/1218946): With MPArch there may be multiple main |
| // frames. This caller was converted automatically to the primary main frame |
| // to preserve its semantics. Follow up to confirm correctness. |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return; |
| |
| // This function should not be called reentrantly. |
| DCHECK(!navigation_starting_); |
| |
| DCHECK(!base::Contains(navigation_map_, navigation_handle)); |
| navigation_map_[navigation_handle] = |
| std::make_unique<NavigationImpl>(navigation_handle); |
| auto* navigation = navigation_map_[navigation_handle].get(); |
| base::AutoReset<NavigationImpl*> auto_reset(&navigation_starting_, |
| navigation); |
| navigation->set_safe_to_set_request_headers(true); |
| navigation->set_safe_to_disable_network_error_auto_reload(true); |
| navigation->set_safe_to_disable_intent_processing(true); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Desktop mode and per-navigation UA use the same mechanism and so don't |
| // interact well. It's not possible to support both at the same time since |
| // if there's a per-navigation UA active and desktop mode is turned on, or |
| // was on previously, the WebContent's state would have to change before |
| // navigation even though that would be wrong for the previous navigation if |
| // the new navigation didn't commit. |
| if (!TabImpl::FromWebContents(web_contents())->desktop_user_agent_enabled()) |
| #endif |
| navigation->set_safe_to_set_user_agent(true); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| NavigationUIDataImpl* navigation_ui_data = static_cast<NavigationUIDataImpl*>( |
| navigation_handle->GetNavigationUIData()); |
| if (navigation_ui_data) { |
| auto response = navigation_ui_data->TakeResponse(); |
| if (response) |
| navigation->SetResponse(std::move(response)); |
| } |
| |
| if (java_controller_) { |
| JNIEnv* env = AttachCurrentThread(); |
| { |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_createNavigation"); |
| ScopedJavaLocalRef<jobject> java_navigation = |
| Java_NavigationControllerImpl_createNavigation( |
| env, java_controller_, reinterpret_cast<jlong>(navigation)); |
| navigation->SetJavaNavigation( |
| base::android::ScopedJavaGlobalRef<jobject>(java_navigation)); |
| } |
| TRACE_EVENT0("weblayer", "Java_NavigationControllerImpl_navigationStarted"); |
| Java_NavigationControllerImpl_navigationStarted( |
| env, java_controller_, navigation->java_navigation()); |
| } |
| #endif |
| for (auto& observer : observers_) |
| observer.NavigationStarted(navigation); |
| navigation->set_safe_to_set_user_agent(false); |
| navigation->set_safe_to_set_request_headers(false); |
| navigation->set_safe_to_disable_network_error_auto_reload(false); |
| navigation->set_safe_to_disable_intent_processing(false); |
| } |
| |
| void NavigationControllerImpl::DidRedirectNavigation( |
| content::NavigationHandle* navigation_handle) { |
| // NOTE: this implementation should remain empty. Real implementation is in |
| // WillRedirectNavigation(). See description of NavigationThrottleImpl for |
| // more information. |
| } |
| |
| void NavigationControllerImpl::ReadyToCommitNavigation( |
| content::NavigationHandle* navigation_handle) { |
| #if BUILDFLAG(IS_ANDROID) |
| // TODO(https://crbug.com/1218946): With MPArch there may be multiple main |
| // frames. This caller was converted automatically to the primary main frame |
| // to preserve its semantics. Follow up to confirm correctness. |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return; |
| |
| DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end()); |
| auto* navigation = navigation_map_[navigation_handle].get(); |
| if (java_controller_) { |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_readyToCommitNavigation"); |
| Java_NavigationControllerImpl_readyToCommitNavigation( |
| AttachCurrentThread(), java_controller_, navigation->java_navigation()); |
| } |
| #endif |
| } |
| |
| void NavigationControllerImpl::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| // TODO(https://crbug.com/1218946): With MPArch there may be multiple main |
| // frames. This caller was converted automatically to the primary main frame |
| // to preserve its semantics. Follow up to confirm correctness. |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return; |
| |
| DelayDeletionHelper deletion_helper(this); |
| DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end()); |
| auto* navigation = navigation_map_[navigation_handle].get(); |
| |
| navigation->set_finished(); |
| |
| if (navigation_handle->HasCommitted()) { |
| // Set state on NavigationEntry user data if a per-navigation user agent was |
| // specified. This can't be done earlier because a NavigationEntry might not |
| // have existed at the time that SetUserAgentString was called. |
| if (navigation->set_user_agent_string_called()) { |
| auto* entry = web_contents()->GetController().GetLastCommittedEntry(); |
| if (entry) { |
| auto* entry_data = NavigationEntryData::Get(entry); |
| if (entry_data) |
| entry_data->set_per_navigation_user_agent_override(true); |
| } |
| } |
| |
| auto* rfh = navigation_handle->GetRenderFrameHost(); |
| PageImpl::GetOrCreateForPage(rfh->GetPage()); |
| navigation->set_safe_to_get_page(); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Ensure that the Java-side Page object for this navigation is |
| // populated from and linked to the native Page object. Without this |
| // call, the Java-side navigation object won't be created and linked to |
| // the native object until/unless the client calls Navigation#getPage(), |
| // which is problematic when implementation-side callers need to bridge |
| // the C++ Page object into Java (e.g., to fire |
| // NavigationCallback#onPageLanguageDetermined()). |
| Java_NavigationControllerImpl_getOrCreatePageForNavigation( |
| AttachCurrentThread(), java_controller_, navigation->java_navigation()); |
| #endif |
| } |
| |
| // In some corner cases (e.g., a tab closing with an ongoing navigation) |
| // navigations finish without committing but without any other error state. |
| // Such navigations are regarded as failed by WebLayer. |
| if (navigation_handle->HasCommitted() && |
| navigation_handle->GetNetErrorCode() == net::OK && |
| !navigation_handle->IsErrorPage()) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (java_controller_) { |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_navigationCompleted"); |
| Java_NavigationControllerImpl_navigationCompleted( |
| AttachCurrentThread(), java_controller_, |
| navigation->java_navigation()); |
| if (deletion_helper.WasControllerDeleted()) |
| return; |
| } |
| #endif |
| for (auto& observer : observers_) { |
| observer.NavigationCompleted(navigation); |
| if (deletion_helper.WasControllerDeleted()) |
| return; |
| } |
| } else { |
| #if BUILDFLAG(IS_ANDROID) |
| if (java_controller_) { |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_navigationFailed"); |
| Java_NavigationControllerImpl_navigationFailed( |
| AttachCurrentThread(), java_controller_, |
| navigation->java_navigation()); |
| if (deletion_helper.WasControllerDeleted()) |
| return; |
| } |
| #endif |
| for (auto& observer : observers_) { |
| observer.NavigationFailed(navigation); |
| if (deletion_helper.WasControllerDeleted()) |
| return; |
| } |
| } |
| |
| // Note InsertVisualStateCallback currently does not take into account |
| // any delays from surface sync, ie a frame submitted by renderer may not |
| // be displayed immediately. Such situations should be rare however, so |
| // this should be good enough for the purposes needed. |
| web_contents()->GetMainFrame()->InsertVisualStateCallback(base::BindOnce( |
| &NavigationControllerImpl::OldPageNoLongerRendered, |
| weak_ptr_factory_.GetWeakPtr(), navigation_handle->GetURL())); |
| |
| navigation_map_.erase(navigation_map_.find(navigation_handle)); |
| } |
| |
| void NavigationControllerImpl::DidStartLoading() { |
| NotifyLoadStateChanged(); |
| } |
| |
| void NavigationControllerImpl::DidStopLoading() { |
| NotifyLoadStateChanged(); |
| } |
| |
| void NavigationControllerImpl::LoadProgressChanged(double progress) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (java_controller_) { |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_loadProgressChanged"); |
| Java_NavigationControllerImpl_loadProgressChanged( |
| AttachCurrentThread(), java_controller_, progress); |
| } |
| #endif |
| for (auto& observer : observers_) |
| observer.LoadProgressChanged(progress); |
| } |
| |
| void NavigationControllerImpl::DidFirstVisuallyNonEmptyPaint() { |
| #if BUILDFLAG(IS_ANDROID) |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_onFirstContentfulPaint"); |
| Java_NavigationControllerImpl_onFirstContentfulPaint(AttachCurrentThread(), |
| java_controller_); |
| #endif |
| |
| for (auto& observer : observers_) |
| observer.OnFirstContentfulPaint(); |
| } |
| |
| void NavigationControllerImpl::OldPageNoLongerRendered(const GURL& url, |
| bool success) { |
| #if BUILDFLAG(IS_ANDROID) |
| TRACE_EVENT0("weblayer", |
| "Java_NavigationControllerImpl_onOldPageNoLongerRendered"); |
| JNIEnv* env = AttachCurrentThread(); |
| Java_NavigationControllerImpl_onOldPageNoLongerRendered( |
| env, java_controller_, |
| base::android::ConvertUTF8ToJavaString(env, url.spec())); |
| #endif |
| for (auto& observer : observers_) |
| observer.OnOldPageNoLongerRendered(url); |
| } |
| |
| void NavigationControllerImpl::NotifyLoadStateChanged() { |
| #if BUILDFLAG(IS_ANDROID) |
| if (java_controller_) { |
| TRACE_EVENT0("weblayer", "Java_NavigationControllerImpl_loadStateChanged"); |
| Java_NavigationControllerImpl_loadStateChanged( |
| AttachCurrentThread(), java_controller_, web_contents()->IsLoading(), |
| web_contents()->ShouldShowLoadingUI()); |
| } |
| #endif |
| for (auto& observer : observers_) { |
| observer.LoadStateChanged(web_contents()->IsLoading(), |
| web_contents()->ShouldShowLoadingUI()); |
| } |
| } |
| |
| void NavigationControllerImpl::DoNavigate( |
| std::unique_ptr<content::NavigationController::LoadURLParams> params) { |
| CancelDelayedLoad(); |
| |
| // Navigations should use the default user-agent (which may be overridden if |
| // desktop mode is turned on). If the embedder wants a custom user-agent, the |
| // embedder will call Navigation::SetUserAgentString() in DidStartNavigation. |
| #if BUILDFLAG(IS_ANDROID) |
| // We need to set UA_OVERRIDE_FALSE if per navigation UA is set. However at |
| // this point we don't know if the embedder will call that later. Since we |
| // ensure that the two can't be set at the same time, it's sufficient to |
| // not enable it if desktop mode is turned on. |
| if (!TabImpl::FromWebContents(web_contents())->desktop_user_agent_enabled()) |
| #endif |
| params->override_user_agent = |
| content::NavigationController::UA_OVERRIDE_FALSE; |
| if (navigation_starting_ || active_throttle_) { |
| // DoNavigate() is being called reentrantly. Delay processing until it's |
| // safe. |
| Stop(); |
| ScheduleDelayedLoad(std::move(params)); |
| return; |
| } |
| |
| params->has_user_gesture = true; |
| web_contents()->GetController().LoadURLWithParams(*params); |
| // So that if the user had entered the UI in a bar it stops flashing the |
| // caret. |
| web_contents()->Focus(); |
| } |
| |
| void NavigationControllerImpl::ScheduleDelayedLoad( |
| std::unique_ptr<content::NavigationController::LoadURLParams> params) { |
| delayed_load_params_ = std::move(params); |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&NavigationControllerImpl::ProcessDelayedLoad, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NavigationControllerImpl::CancelDelayedLoad() { |
| delayed_load_params_.reset(); |
| } |
| |
| void NavigationControllerImpl::ProcessDelayedLoad() { |
| if (delayed_load_params_) |
| DoNavigate(std::move(delayed_load_params_)); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| static jlong JNI_NavigationControllerImpl_GetNavigationController(JNIEnv* env, |
| jlong tab) { |
| return reinterpret_cast<jlong>( |
| reinterpret_cast<Tab*>(tab)->GetNavigationController()); |
| } |
| #endif |
| |
| } // namespace weblayer |