| // 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. |
| |
| #include "content/browser/renderer_host/navigation_controller_android.h" |
| |
| #include <stdint.h> |
| |
| #include <string> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_string.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/flat_map.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "content/browser/renderer_host/navigation_controller_impl.h" |
| #include "content/browser/renderer_host/navigation_entry_impl.h" |
| #include "content/public/android/content_jni_headers/NavigationControllerImpl_jni.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/ssl_host_state_delegate.h" |
| #include "content/public/common/referrer.h" |
| #include "content/public/common/resource_request_body_android.h" |
| #include "content/public/common/url_constants.h" |
| #include "net/base/data_url.h" |
| #include "ui/gfx/android/java_bitmap.h" |
| #include "url/android/gurl_android.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| using base::android::AttachCurrentThread; |
| using base::android::ConvertJavaStringToUTF16; |
| using base::android::ConvertJavaStringToUTF8; |
| using base::android::ConvertUTF16ToJavaString; |
| using base::android::JavaParamRef; |
| using base::android::JavaRef; |
| using base::android::ScopedJavaLocalRef; |
| |
| namespace { |
| |
| const char kMapDataKey[] = "map_data_key"; |
| |
| // static |
| static base::android::ScopedJavaLocalRef<jobject> |
| JNI_NavigationControllerImpl_CreateJavaNavigationEntry( |
| JNIEnv* env, |
| content::NavigationEntry* entry, |
| int index) { |
| DCHECK(entry); |
| |
| // Get the details of the current entry |
| ScopedJavaLocalRef<jobject> j_url( |
| url::GURLAndroid::FromNativeGURL(env, entry->GetURL())); |
| ScopedJavaLocalRef<jobject> j_virtual_url( |
| url::GURLAndroid::FromNativeGURL(env, entry->GetVirtualURL())); |
| ScopedJavaLocalRef<jobject> j_original_url( |
| url::GURLAndroid::FromNativeGURL(env, entry->GetOriginalRequestURL())); |
| ScopedJavaLocalRef<jstring> j_title( |
| ConvertUTF16ToJavaString(env, entry->GetTitle())); |
| ScopedJavaLocalRef<jobject> j_referrer_url( |
| url::GURLAndroid::FromNativeGURL(env, entry->GetReferrer().url)); |
| ScopedJavaLocalRef<jobject> j_bitmap; |
| const content::FaviconStatus& status = entry->GetFavicon(); |
| if (status.valid && status.image.ToSkBitmap()->computeByteSize() > 0) { |
| j_bitmap = gfx::ConvertToJavaBitmap(*status.image.ToSkBitmap(), |
| gfx::OomBehavior::kReturnNullOnOom); |
| } |
| jlong j_timestamp = entry->GetTimestamp().ToJavaTime(); |
| |
| return content::Java_NavigationControllerImpl_createNavigationEntry( |
| env, index, j_url, j_virtual_url, j_original_url, j_referrer_url, j_title, |
| j_bitmap, entry->GetTransitionType(), j_timestamp, |
| entry->IsInitialEntry()); |
| } |
| |
| static void JNI_NavigationControllerImpl_AddNavigationEntryToHistory( |
| JNIEnv* env, |
| const JavaRef<jobject>& history, |
| content::NavigationEntry* entry, |
| int index) { |
| content::Java_NavigationControllerImpl_addToNavigationHistory( |
| env, history, |
| JNI_NavigationControllerImpl_CreateJavaNavigationEntry(env, entry, |
| index)); |
| } |
| |
| class MapData : public base::SupportsUserData::Data { |
| public: |
| MapData() = default; |
| |
| MapData(const MapData&) = delete; |
| MapData& operator=(const MapData&) = delete; |
| |
| ~MapData() override = default; |
| |
| static MapData* Get(content::NavigationEntry* entry) { |
| MapData* map_data = static_cast<MapData*>(entry->GetUserData(kMapDataKey)); |
| if (map_data) |
| return map_data; |
| auto map_data_ptr = std::make_unique<MapData>(); |
| map_data = map_data_ptr.get(); |
| entry->SetUserData(kMapDataKey, std::move(map_data_ptr)); |
| return map_data; |
| } |
| |
| base::flat_map<std::string, std::u16string>& map() { return map_; } |
| |
| // base::SupportsUserData::Data: |
| std::unique_ptr<Data> Clone() override { |
| std::unique_ptr<MapData> clone = std::make_unique<MapData>(); |
| clone->map_ = map_; |
| return clone; |
| } |
| |
| private: |
| base::flat_map<std::string, std::u16string> map_; |
| }; |
| |
| } // namespace |
| |
| namespace content { |
| |
| NavigationControllerAndroid::NavigationControllerAndroid( |
| NavigationControllerImpl* navigation_controller) |
| : navigation_controller_(navigation_controller) { |
| JNIEnv* env = AttachCurrentThread(); |
| obj_.Reset(env, Java_NavigationControllerImpl_create( |
| env, reinterpret_cast<intptr_t>(this)) |
| .obj()); |
| } |
| |
| NavigationControllerAndroid::~NavigationControllerAndroid() { |
| Java_NavigationControllerImpl_destroy(AttachCurrentThread(), obj_); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| NavigationControllerAndroid::GetJavaObject() { |
| return base::android::ScopedJavaLocalRef<jobject>(obj_); |
| } |
| |
| jboolean NavigationControllerAndroid::CanGoBack( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| return navigation_controller_->CanGoBack(); |
| } |
| |
| jboolean NavigationControllerAndroid::CanGoForward( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| return navigation_controller_->CanGoForward(); |
| } |
| |
| jboolean NavigationControllerAndroid::CanGoToOffset( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jint offset) { |
| return navigation_controller_->CanGoToOffsetWithSkipping(offset); |
| } |
| |
| void NavigationControllerAndroid::GoBack(JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| navigation_controller_->GoBack(); |
| } |
| |
| void NavigationControllerAndroid::GoForward(JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| navigation_controller_->GoForward(); |
| } |
| |
| void NavigationControllerAndroid::GoToOffset(JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jint offset) { |
| navigation_controller_->GoToOffsetWithSkipping(offset); |
| } |
| |
| jboolean NavigationControllerAndroid::IsInitialNavigation( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| return navigation_controller_->IsInitialNavigation(); |
| } |
| |
| void NavigationControllerAndroid::LoadIfNecessary( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| navigation_controller_->LoadIfNecessary(); |
| } |
| |
| void NavigationControllerAndroid::ContinuePendingReload( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| navigation_controller_->ContinuePendingReload(); |
| } |
| |
| void NavigationControllerAndroid::Reload(JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jboolean check_for_repost) { |
| SCOPED_CRASH_KEY_BOOL("nav_reentrancy_caller2", "Reload_check", |
| (bool)check_for_repost); |
| navigation_controller_->Reload(ReloadType::NORMAL, check_for_repost); |
| } |
| |
| void NavigationControllerAndroid::ReloadBypassingCache( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jboolean check_for_repost) { |
| SCOPED_CRASH_KEY_BOOL("nav_reentrancy_caller2", "ReloadB_check", |
| (bool)check_for_repost); |
| navigation_controller_->Reload(ReloadType::BYPASSING_CACHE, check_for_repost); |
| } |
| |
| jboolean NavigationControllerAndroid::NeedsReload( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| return navigation_controller_->NeedsReload(); |
| } |
| |
| void NavigationControllerAndroid::SetNeedsReload( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| navigation_controller_->SetNeedsReload(); |
| } |
| |
| void NavigationControllerAndroid::CancelPendingReload( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| navigation_controller_->CancelPendingReload(); |
| } |
| |
| void NavigationControllerAndroid::GoToNavigationIndex( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jint index) { |
| navigation_controller_->GoToIndex(index); |
| } |
| |
| void NavigationControllerAndroid::LoadUrl( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jstring>& url, |
| jint load_url_type, |
| jint transition_type, |
| const JavaParamRef<jstring>& j_referrer_url, |
| jint referrer_policy, |
| jint ua_override_option, |
| const JavaParamRef<jstring>& extra_headers, |
| const JavaParamRef<jobject>& j_post_data, |
| const JavaParamRef<jstring>& base_url_for_data_url, |
| const JavaParamRef<jstring>& virtual_url_for_data_url, |
| const JavaParamRef<jstring>& data_url_as_string, |
| jboolean can_load_local_resources, |
| jboolean is_renderer_initiated, |
| jboolean should_replace_current_entry, |
| const JavaParamRef<jobject>& j_initiator_origin, |
| jboolean has_user_gesture, |
| jboolean should_clear_history_list, |
| jlong input_start, |
| jlong navigation_ui_data_ptr) { |
| DCHECK(url); |
| NavigationController::LoadURLParams params( |
| GURL(ConvertJavaStringToUTF8(env, url))); |
| // Wrap the raw pointer in case on an early return. |
| std::unique_ptr<NavigationUIData> navigation_ui_data = base::WrapUnique( |
| reinterpret_cast<NavigationUIData*>(navigation_ui_data_ptr)); |
| params.load_type = |
| static_cast<NavigationController::LoadURLType>(load_url_type); |
| params.transition_type = ui::PageTransitionFromInt(transition_type); |
| params.override_user_agent = |
| static_cast<NavigationController::UserAgentOverrideOption>( |
| ua_override_option); |
| params.can_load_local_resources = can_load_local_resources; |
| params.is_renderer_initiated = is_renderer_initiated; |
| params.should_replace_current_entry = should_replace_current_entry; |
| params.has_user_gesture = has_user_gesture; |
| params.should_clear_history_list = should_clear_history_list; |
| |
| if (extra_headers) |
| params.extra_headers = ConvertJavaStringToUTF8(env, extra_headers); |
| |
| params.post_data = ExtractResourceRequestBodyFromJavaObject(env, j_post_data); |
| |
| if (base_url_for_data_url) { |
| params.base_url_for_data_url = |
| GURL(ConvertJavaStringToUTF8(env, base_url_for_data_url)); |
| } |
| |
| if (virtual_url_for_data_url) { |
| params.virtual_url_for_data_url = |
| GURL(ConvertJavaStringToUTF8(env, virtual_url_for_data_url)); |
| } |
| |
| if (data_url_as_string) { |
| // Treat |data_url_as_string| as if we were intending to put it into a GURL |
| // field. Note that kMaxURLChars is only enforced when serializing URLs |
| // for IPC. |
| GURL data_url = GURL(ConvertJavaStringToUTF8(env, data_url_as_string)); |
| DCHECK(data_url.SchemeIs(url::kDataScheme)); |
| DCHECK(params.url.SchemeIs(url::kDataScheme)); |
| #if DCHECK_IS_ON() |
| { |
| std::string mime_type, charset, data; |
| DCHECK(net::DataURL::Parse(params.url, &mime_type, &charset, &data)); |
| DCHECK(data.empty()); |
| } |
| #endif |
| std::string s = data_url.spec(); |
| params.data_url_as_string = base::RefCountedString::TakeString(&s); |
| } |
| |
| if (j_referrer_url) { |
| params.referrer = |
| Referrer(GURL(ConvertJavaStringToUTF8(env, j_referrer_url)), |
| Referrer::ConvertToPolicy(referrer_policy)); |
| } |
| |
| if (j_initiator_origin) { |
| params.initiator_origin = url::Origin::FromJavaObject(j_initiator_origin); |
| } |
| |
| if (input_start != 0) |
| params.input_start = base::TimeTicks::FromUptimeMillis(input_start); |
| |
| params.navigation_ui_data = std::move(navigation_ui_data); |
| |
| navigation_controller_->LoadURLWithParams(params); |
| } |
| |
| void NavigationControllerAndroid::ClearHistory( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| // TODO(creis): Do callers of this need to know if it fails? |
| if (navigation_controller_->CanPruneAllButLastCommitted()) |
| navigation_controller_->PruneAllButLastCommitted(); |
| } |
| |
| jint NavigationControllerAndroid::GetNavigationHistory( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jobject>& history) { |
| // Iterate through navigation entries to populate the list |
| int count = navigation_controller_->GetEntryCount(); |
| for (int i = 0; i < count; ++i) { |
| JNI_NavigationControllerImpl_AddNavigationEntryToHistory( |
| env, history, navigation_controller_->GetEntryAtIndex(i), i); |
| } |
| |
| return navigation_controller_->GetCurrentEntryIndex(); |
| } |
| |
| void NavigationControllerAndroid::GetDirectedNavigationHistory( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jobject>& history, |
| jboolean is_forward, |
| jint max_entries) { |
| // Iterate through navigation entries to populate the list |
| int count = navigation_controller_->GetEntryCount(); |
| int num_added = 0; |
| int increment_value = is_forward ? 1 : -1; |
| for (int i = navigation_controller_->GetCurrentEntryIndex() + increment_value; |
| i >= 0 && i < count; i += increment_value) { |
| if (num_added >= max_entries) |
| break; |
| |
| JNI_NavigationControllerImpl_AddNavigationEntryToHistory( |
| env, history, navigation_controller_->GetEntryAtIndex(i), i); |
| num_added++; |
| } |
| } |
| |
| void NavigationControllerAndroid::ClearSslPreferences( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| SSLHostStateDelegate* delegate = |
| navigation_controller_->GetBrowserContext()->GetSSLHostStateDelegate(); |
| if (delegate) |
| delegate->Clear(base::NullCallback()); |
| } |
| |
| bool NavigationControllerAndroid::GetUseDesktopUserAgent( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| NavigationEntry* entry = navigation_controller_->GetLastCommittedEntry(); |
| return entry && entry->GetIsOverridingUserAgent(); |
| } |
| |
| void NavigationControllerAndroid::SetUseDesktopUserAgent( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jboolean enabled, |
| jboolean reload_on_state_change, |
| jint source) { |
| SCOPED_CRASH_KEY_BOOL("nav_reentrancy_caller2", "SetUA_enabled", |
| (bool)enabled); |
| if (GetUseDesktopUserAgent(env, obj) == enabled) |
| return; |
| |
| if (navigation_controller_->in_navigate_to_pending_entry() && |
| reload_on_state_change) { |
| // Sometimes it's possible to call this function in response to a |
| // navigation to a pending entry. In this case, we should avoid triggering |
| // another navigation synchronously, as it will crash due to navigation |
| // re-entrancy checks. To do that, post a task to update the UA and |
| // reload asynchronously. |
| // TODO(https://crbug.com/1327907): Figure out the case that leads to this |
| // situation and avoid calling this function entirely in that case. For now, |
| // do a do a DumpWithoutCrashing so that we can investigate. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &NavigationControllerAndroid::SetUseDesktopUserAgentInternal, |
| weak_factory_.GetWeakPtr(), enabled, reload_on_state_change)); |
| LOG(WARNING) << "NavigationControllerAndroid::SetUseDesktopUserAgent " |
| << "triggers re-entrant navigation, override: " |
| << (bool)enabled << ", source: " << (int)source; |
| SCOPED_CRASH_KEY_NUMBER("SetUseDesktopUserAgent", "caller", (int)source); |
| base::debug::DumpWithoutCrashing(); |
| } else { |
| SetUseDesktopUserAgentInternal(enabled, reload_on_state_change); |
| } |
| } |
| |
| void NavigationControllerAndroid::SetUseDesktopUserAgentInternal( |
| bool enabled, |
| bool reload_on_state_change) { |
| // Make sure the navigation entry actually exists. |
| NavigationEntry* entry = navigation_controller_->GetLastCommittedEntry(); |
| if (!entry) |
| return; |
| |
| // Set the flag in the NavigationEntry. |
| entry->SetIsOverridingUserAgent(enabled); |
| navigation_controller_->delegate()->UpdateOverridingUserAgent(); |
| |
| // Send the override to the renderer. |
| if (reload_on_state_change) { |
| // Reloading the page will send the override down as part of the |
| // navigation IPC message. |
| navigation_controller_->Reload(ReloadType::ORIGINAL_REQUEST_URL, true); |
| } |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| NavigationControllerAndroid::GetEntryAtIndex(JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| int index) { |
| if (index < 0 || index >= navigation_controller_->GetEntryCount()) |
| return base::android::ScopedJavaLocalRef<jobject>(); |
| |
| NavigationEntry* entry = navigation_controller_->GetEntryAtIndex(index); |
| return JNI_NavigationControllerImpl_CreateJavaNavigationEntry(env, entry, |
| index); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| NavigationControllerAndroid::GetVisibleEntry(JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| NavigationEntry* entry = navigation_controller_->GetVisibleEntry(); |
| |
| if (!entry) |
| return base::android::ScopedJavaLocalRef<jobject>(); |
| |
| return JNI_NavigationControllerImpl_CreateJavaNavigationEntry(env, entry, |
| /*index=*/-1); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| NavigationControllerAndroid::GetPendingEntry(JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| NavigationEntry* entry = navigation_controller_->GetPendingEntry(); |
| |
| if (!entry) |
| return base::android::ScopedJavaLocalRef<jobject>(); |
| |
| return JNI_NavigationControllerImpl_CreateJavaNavigationEntry( |
| env, entry, navigation_controller_->GetPendingEntryIndex()); |
| } |
| |
| jint NavigationControllerAndroid::GetLastCommittedEntryIndex( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| return navigation_controller_->GetLastCommittedEntryIndex(); |
| } |
| |
| jboolean NavigationControllerAndroid::RemoveEntryAtIndex( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jint index) { |
| return navigation_controller_->RemoveEntryAtIndex(index); |
| } |
| |
| void NavigationControllerAndroid::PruneForwardEntries( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| return navigation_controller_->PruneForwardEntries(); |
| } |
| |
| ScopedJavaLocalRef<jstring> NavigationControllerAndroid::GetEntryExtraData( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jint index, |
| const JavaParamRef<jstring>& jkey) { |
| if (index < 0 || index >= navigation_controller_->GetEntryCount()) |
| return ScopedJavaLocalRef<jstring>(); |
| |
| std::string key = base::android::ConvertJavaStringToUTF8(env, jkey); |
| MapData* map_data = |
| MapData::Get(navigation_controller_->GetEntryAtIndex(index)); |
| auto iter = map_data->map().find(key); |
| return ConvertUTF16ToJavaString( |
| env, iter == map_data->map().end() ? std::u16string() : iter->second); |
| } |
| |
| void NavigationControllerAndroid::SetEntryExtraData( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jint index, |
| const JavaParamRef<jstring>& jkey, |
| const JavaParamRef<jstring>& jvalue) { |
| if (index < 0 || index >= navigation_controller_->GetEntryCount()) |
| return; |
| |
| std::string key = base::android::ConvertJavaStringToUTF8(env, jkey); |
| std::u16string value = base::android::ConvertJavaStringToUTF16(env, jvalue); |
| MapData* map_data = |
| MapData::Get(navigation_controller_->GetEntryAtIndex(index)); |
| map_data->map()[key] = value; |
| } |
| |
| jboolean NavigationControllerAndroid::IsEntryMarkedToBeSkipped( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& obj, |
| jint index) { |
| return navigation_controller_->IsEntryMarkedToBeSkipped(index); |
| } |
| |
| } // namespace content |