| // 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/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 |