|  | // Copyright 2013 The Chromium Authors | 
|  | // 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/containers/flat_map.h" | 
|  | #include "base/debug/crash_logging.h" | 
|  | #include "base/debug/dump_without_crashing.h" | 
|  | #include "base/functional/callback.h" | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "content/browser/android/impression_utils.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/navigation_handle.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_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_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); | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaGlobalRef<jobject> | 
|  | 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, | 
|  | const base::android::JavaParamRef<jobject>& j_impression, | 
|  | 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 (j_impression) { | 
|  | params.initiator_frame_token = | 
|  | GetInitiatorFrameTokenFromJavaImpression(env, j_impression); | 
|  | params.initiator_process_id = | 
|  | GetInitiatorProcessIDFromJavaImpression(env, j_impression); | 
|  | blink::Impression impression; | 
|  | impression.attribution_src_token = | 
|  | GetAttributionSrcTokenFromJavaImpression(env, j_impression).value(); | 
|  | impression.runtime_features = | 
|  | GetAttributionRuntimeFeaturesFromJavaImpression(env, j_impression); | 
|  | params.impression = impression; | 
|  | } | 
|  |  | 
|  | 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::MakeRefCounted<base::RefCountedString>(std::move(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); | 
|  |  | 
|  | base::WeakPtr<NavigationHandle> handle = | 
|  | navigation_controller_->LoadURLWithParams(params); | 
|  |  | 
|  | if (!handle) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return base::android::ScopedJavaGlobalRef<jobject>( | 
|  | handle->GetJavaNavigationHandle()); | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | // TODO(crbug.com/1414625): Early return for initial NavigationEntries as a | 
|  | // workaround. Currently, doing a reload while on the initial NavigationEntry | 
|  | // might result in committing an unrelated pending NavigationEntry and | 
|  | // mistakenly marking that entry as an initial NavigationEntry. That will | 
|  | // cause problems, such as the URL bar showing about:blank instead of the URL | 
|  | // of the NavigationEntry. To prevent that happening in this case, skip | 
|  | // reloading initial NavigationEntries entirely. This is a short-term fix, | 
|  | // while we work on a long-term fix to no longer mistakenly mark the unrelated | 
|  | // pending NavigationEntry as the initial NavigationEntry. | 
|  | if (!entry || entry->IsInitialEntry()) { | 
|  | 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 |