// Copyright 2014 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 "chrome/browser/android/tab_state.h" #include #include #include #include "base/android/jni_android.h" #include "base/android/jni_string.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/pickle.h" #include "chrome/browser/android/tab_android.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "components/sessions/content/content_serialized_navigation_builder.h" #include "components/sessions/serialized_navigation_entry.h" #include "components/sessions/session_command.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/web_contents.h" #include "jni/TabState_jni.h" using base::android::ConvertUTF16ToJavaString; using base::android::ConvertUTF8ToJavaString; using base::android::ScopedJavaLocalRef; using content::NavigationController; using content::WebContents; namespace { bool WriteStateHeaderToPickle(bool off_the_record, int entry_count, int current_entry_index, Pickle* pickle) { return pickle->WriteBool(off_the_record) && pickle->WriteInt(entry_count) && pickle->WriteInt(current_entry_index); } // Migrates a pickled SerializedNavigationEntry from Android tab version 0 to // 2 or (Chrome 18->26). // // Due to the fact that all SerializedNavigationEntrys were previously stored // in a single pickle on Android, this function has to read the fields exactly // how they were written on m18 which is a custom format and different other // chromes. // // This uses the fields from SerializedNavigationEntry/TabNavigation from: // https://gerrit-int.chromium.org/gitweb?p=clank/internal/apps.git; // a=blob;f=native/framework/chrome/tab.cc;hb=refs/heads/m18 // // 1. For each tab navigation: // virtual_url // title // content_state // transition_type // type_mask // // 2. For each tab navigation: // referrer // is_overriding_user_agent // void UpgradeNavigationFromV0ToV2( std::vector* navigations, int entry_count, PickleIterator* iterator) { for (int i = 0; i < entry_count; ++i) { Pickle v2_pickle; std::string virtual_url_spec; std::string str_referrer; base::string16 title; std::string content_state; int transition_type_int; if (!iterator->ReadString(&virtual_url_spec) || !iterator->ReadString(&str_referrer) || !iterator->ReadString16(&title) || !iterator->ReadString(&content_state) || !iterator->ReadInt(&transition_type_int)) return; // Write back the fields that were just read. v2_pickle.WriteInt(i); v2_pickle.WriteString(virtual_url_spec); v2_pickle.WriteString16(title); v2_pickle.WriteString(content_state); v2_pickle.WriteInt(transition_type_int); // type_mask v2_pickle.WriteInt(0); // referrer_spec v2_pickle.WriteString(str_referrer); // policy_int v2_pickle.WriteInt(0); // original_request_url_spec v2_pickle.WriteString(std::string()); // is_overriding_user_agent v2_pickle.WriteBool(false); // timestamp_internal_value v2_pickle.WriteInt64(0); // search_terms v2_pickle.WriteString16(base::string16()); PickleIterator tab_navigation_pickle_iterator(v2_pickle); sessions::SerializedNavigationEntry nav; if (nav.ReadFromPickle(&tab_navigation_pickle_iterator)) { navigations->push_back(nav); } else { LOG(ERROR) << "Failed to read SerializedNavigationEntry from pickle " << "(index=" << i << ", url=" << virtual_url_spec; } } for (int i = 0; i < entry_count; ++i) { std::string initial_url; bool user_agent_overridden; if (!iterator->ReadString(&initial_url) || !iterator->ReadBool(&user_agent_overridden)) { break; } } } // Migrates a pickled SerializedNavigationEntry from Android tab version 0 to 1 // (or Chrome 25->26) // // Due to the fact that all SerializedNavigationEntrys were previously stored in // a single pickle on Android, this function reads all the old fields, // re-outputs them and appends an empty string16, representing the new // search_terms field, and ensures that reading a v0 SerializedNavigationEntry // won't consume bytes from a subsequent SerializedNavigationEntry. // // This uses the fields from SerializedNavigationEntry/TabNavigation prior to // https://chromiumcodereview.appspot.com/11876045 which are: // // index // virtual_url // title // content_state // transition_type // type_mask // referrer // original_request_url // is_overriding_user_agent // timestamp // // And finally search_terms was added and this function appends it. void UpgradeNavigationFromV1ToV2( std::vector* navigations, int entry_count, PickleIterator* iterator) { for (int i = 0; i < entry_count; ++i) { Pickle v2_pickle; int index; std::string virtual_url_spec; base::string16 title; std::string content_state; int transition_type_int; if (!iterator->ReadInt(&index) || !iterator->ReadString(&virtual_url_spec) || !iterator->ReadString16(&title) || !iterator->ReadString(&content_state) || !iterator->ReadInt(&transition_type_int)) return; // Write back the fields that were just read. v2_pickle.WriteInt(index); v2_pickle.WriteString(virtual_url_spec); v2_pickle.WriteString16(title); v2_pickle.WriteString(content_state); v2_pickle.WriteInt(transition_type_int); int type_mask = 0; if (!iterator->ReadInt(&type_mask)) continue; v2_pickle.WriteInt(type_mask); std::string referrer_spec; if (iterator->ReadString(&referrer_spec)) v2_pickle.WriteString(referrer_spec); int policy_int; if (iterator->ReadInt(&policy_int)) v2_pickle.WriteInt(policy_int); std::string original_request_url_spec; if (iterator->ReadString(&original_request_url_spec)) v2_pickle.WriteString(original_request_url_spec); bool is_overriding_user_agent; if (iterator->ReadBool(&is_overriding_user_agent)) v2_pickle.WriteBool(is_overriding_user_agent); int64 timestamp_internal_value = 0; if (iterator->ReadInt64(×tamp_internal_value)) v2_pickle.WriteInt64(timestamp_internal_value); // Force output of search_terms v2_pickle.WriteString16(base::string16()); PickleIterator tab_navigation_pickle_iterator(v2_pickle); sessions::SerializedNavigationEntry nav; if (nav.ReadFromPickle(&tab_navigation_pickle_iterator)) { navigations->push_back(nav); } else { LOG(ERROR) << "Failed to read SerializedNavigationEntry from pickle " << "(index=" << i << ", url=" << virtual_url_spec; } } } // Extracts state and navigation entries from the given Pickle data and returns // whether un-pickling the data succeeded bool ExtractNavigationEntries( void* data, int size, int saved_state_version, bool* is_off_the_record, int* current_entry_index, std::vector* navigations) { int entry_count; Pickle pickle(static_cast(data), size); PickleIterator iter(pickle); if (!iter.ReadBool(is_off_the_record) || !iter.ReadInt(&entry_count) || !iter.ReadInt(current_entry_index)) { LOG(ERROR) << "Failed to restore state from byte array (length=" << size << ")."; return false; } if (!saved_state_version) { // When |saved_state_version| is 0, it predates our notion of each tab // having a saved version id. For that version of tab serialization, we // used a single pickle for all |SerializedNavigationEntry|s. UpgradeNavigationFromV0ToV2(navigations, entry_count, &iter); } else if (saved_state_version == 1) { // When |saved_state_version| is 1, it predates our notion of each tab // having a saved version id. For that version of tab serialization, we // used a single pickle for all |SerializedNavigationEntry|s. UpgradeNavigationFromV1ToV2(navigations, entry_count, &iter); } else { // |saved_state_version| == 2 and greater. for (int i = 0; i < entry_count; ++i) { // Read each SerializedNavigationEntry as a separate pickle to avoid // optional reads of one tab bleeding into the next tab's data. int tab_navigation_data_length = 0; const char* tab_navigation_data = NULL; if (!iter.ReadInt(&tab_navigation_data_length) || !iter.ReadBytes(&tab_navigation_data, tab_navigation_data_length)) { LOG(ERROR) << "Failed to restore tab entry from byte array. " << "(SerializedNavigationEntry size=" << tab_navigation_data_length << ")."; return false; // It's dangerous to keep deserializing now, give up. } Pickle tab_navigation_pickle(tab_navigation_data, tab_navigation_data_length); PickleIterator tab_navigation_pickle_iterator(tab_navigation_pickle); sessions::SerializedNavigationEntry nav; if (!nav.ReadFromPickle(&tab_navigation_pickle_iterator)) return false; // If we failed to read a navigation, give up on others. navigations->push_back(nav); } } // Validate the data. if (*current_entry_index < 0 || *current_entry_index >= static_cast(navigations->size())) return false; return true; } }; // anonymous namespace ScopedJavaLocalRef WebContentsState::GetContentsStateAsByteBuffer( JNIEnv* env, TabAndroid* tab) { Profile* profile = tab->GetProfile(); if (!profile) return ScopedJavaLocalRef(); content::NavigationController& controller = tab->web_contents()->GetController(); const int pending_index = controller.GetPendingEntryIndex(); int entry_count = controller.GetEntryCount(); if (entry_count == 0 && pending_index == 0) entry_count++; if (entry_count == 0) return ScopedJavaLocalRef(); int current_entry = controller.GetLastCommittedEntryIndex(); if (current_entry == -1 && entry_count > 0) current_entry = 0; std::vector navigations(entry_count); for (int i = 0; i < entry_count; ++i) { content::NavigationEntry* entry = (i == pending_index) ? controller.GetPendingEntry() : controller.GetEntryAtIndex(i); navigations[i] = entry; } return WebContentsState::WriteNavigationsAsByteBuffer( env, profile->IsOffTheRecord(), navigations, current_entry); } // Common implementation for GetContentsStateAsByteBuffer() and // CreateContentsStateAsByteBuffer(). Does not assume ownership of the // navigations. ScopedJavaLocalRef WebContentsState::WriteNavigationsAsByteBuffer( JNIEnv* env, bool is_off_the_record, const std::vector& navigations, int current_entry) { Pickle pickle; if (!WriteStateHeaderToPickle(is_off_the_record, navigations.size(), current_entry, &pickle)) { LOG(ERROR) << "Failed to serialize tab state (entry count=" << navigations.size() << ")."; return ScopedJavaLocalRef(); } // Write out all of the NavigationEntrys. for (size_t i = 0; i < navigations.size(); ++i) { // Write each SerializedNavigationEntry as a separate pickle to avoid // optional reads of one tab bleeding into the next tab's data. Pickle tab_navigation_pickle; // Max size taken from BaseSessionService::CreateUpdateTabNavigationCommand. static const size_t max_state_size = std::numeric_limits::max() - 1024; sessions::ContentSerializedNavigationBuilder::FromNavigationEntry( i, *navigations[i]) .WriteToPickle(max_state_size, &tab_navigation_pickle); pickle.WriteInt(tab_navigation_pickle.size()); pickle.WriteBytes(tab_navigation_pickle.data(), tab_navigation_pickle.size()); } void* buffer = malloc(pickle.size()); if (buffer == NULL) { // We can run out of memory allocating a large enough buffer. // In that case we'll only save the current entry. // TODO(jcivelli): http://b/issue?id=5869635 we should save more entries. // more TODO(jcivelli): Make this work return ScopedJavaLocalRef(); } // TODO(yfriedman): Add a |release| to Pickle and save the copy. memcpy(buffer, pickle.data(), pickle.size()); ScopedJavaLocalRef jb(env, env->NewDirectByteBuffer(buffer, pickle.size())); if (base::android::ClearException(env) || jb.is_null()) free(buffer); return jb; } ScopedJavaLocalRef WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv* env, void* data, int size, int saved_state_version) { bool is_off_the_record; int current_entry_index; std::vector navigations; bool success = ExtractNavigationEntries(data, size, saved_state_version, &is_off_the_record, ¤t_entry_index, &navigations); if (!success) return ScopedJavaLocalRef(); sessions::SerializedNavigationEntry nav_entry = navigations.at(current_entry_index); return ConvertUTF16ToJavaString(env, nav_entry.title()); } ScopedJavaLocalRef WebContentsState::GetVirtualUrlFromByteBuffer(JNIEnv* env, void* data, int size, int saved_state_version) { bool is_off_the_record; int current_entry_index; std::vector navigations; bool success = ExtractNavigationEntries(data, size, saved_state_version, &is_off_the_record, ¤t_entry_index, &navigations); if (!success) return ScopedJavaLocalRef(); sessions::SerializedNavigationEntry nav_entry = navigations.at(current_entry_index); return ConvertUTF8ToJavaString(env, nav_entry.virtual_url().spec()); } WebContents* WebContentsState::RestoreContentsFromByteBuffer( void* data, int size, int saved_state_version, bool initially_hidden) { bool is_off_the_record; int current_entry_index; std::vector navigations; bool success = ExtractNavigationEntries(data, size, saved_state_version, &is_off_the_record, ¤t_entry_index, &navigations); if (!success) return NULL; Profile* profile = ProfileManager::GetActiveUserProfile(); ScopedVector scoped_entries = sessions::ContentSerializedNavigationBuilder::ToNavigationEntries( navigations, profile); std::vector entries; scoped_entries.release(&entries); if (is_off_the_record) profile = profile->GetOffTheRecordProfile(); WebContents::CreateParams params(profile); params.initially_hidden = initially_hidden; scoped_ptr web_contents(WebContents::Create(params)); web_contents->GetController().Restore( current_entry_index, NavigationController::RESTORE_CURRENT_SESSION, &entries); return web_contents.release(); } WebContents* WebContentsState::RestoreContentsFromByteBuffer( JNIEnv* env, jclass clazz, jobject state, jint saved_state_version, jboolean initially_hidden) { void* data = env->GetDirectBufferAddress(state); int size = env->GetDirectBufferCapacity(state); return WebContentsState::RestoreContentsFromByteBuffer(data, size, saved_state_version, initially_hidden); } ScopedJavaLocalRef WebContentsState::CreateSingleNavigationStateAsByteBuffer( JNIEnv* env, jstring url, jstring referrer_url, jint referrer_policy, jboolean is_off_the_record) { content::Referrer referrer; if (referrer_url) { referrer = content::Referrer( GURL(base::android::ConvertJavaStringToUTF8(env, referrer_url)), static_cast(referrer_policy)); } scoped_ptr entry( content::NavigationController::CreateNavigationEntry( GURL(base::android::ConvertJavaStringToUTF8(env, url)), referrer, ui::PAGE_TRANSITION_LINK, true, // is_renderer_initiated "", // extra_headers ProfileManager::GetActiveUserProfile())); std::vector navigations(1); navigations[0] = entry.get(); return WebContentsState::WriteNavigationsAsByteBuffer(env, is_off_the_record, navigations, 0); } // Static JNI methods. static void FreeWebContentsStateBuffer(JNIEnv* env, jclass clazz, jobject obj) { void* data = env->GetDirectBufferAddress(obj); free(data); } static jlong RestoreContentsFromByteBuffer(JNIEnv* env, jclass clazz, jobject state, jint saved_state_version, jboolean initially_hidden) { return reinterpret_cast( WebContentsState::RestoreContentsFromByteBuffer(env, clazz, state, saved_state_version, initially_hidden)); } static jobject GetContentsStateAsByteBuffer( JNIEnv* env, jclass clazz, jobject jtab) { TabAndroid* tab_android = TabAndroid::GetNativeTab(env, jtab); return WebContentsState::GetContentsStateAsByteBuffer( env, tab_android).Release(); } static jobject CreateSingleNavigationStateAsByteBuffer( JNIEnv* env, jclass clazz, jstring url, jstring referrer_url, jint referrer_policy, jboolean is_off_the_record) { return WebContentsState::CreateSingleNavigationStateAsByteBuffer( env, url, referrer_url, referrer_policy, is_off_the_record).Release(); } static jstring GetDisplayTitleFromByteBuffer(JNIEnv* env, jclass clazz, jobject state, jint saved_state_version) { void* data = env->GetDirectBufferAddress(state); int size = env->GetDirectBufferCapacity(state); ScopedJavaLocalRef result = WebContentsState::GetDisplayTitleFromByteBuffer( env, data, size, saved_state_version); return result.Release(); } static jstring GetVirtualUrlFromByteBuffer(JNIEnv* env, jclass clazz, jobject state, jint saved_state_version) { void* data = env->GetDirectBufferAddress(state); int size = env->GetDirectBufferCapacity(state); ScopedJavaLocalRef result = WebContentsState::GetVirtualUrlFromByteBuffer( env, data, size, saved_state_version); return result.Release(); } // Creates a historical tab entry from the serialized tab contents contained // within |state|. static void CreateHistoricalTab(JNIEnv* env, jclass clazz, jobject state, jint saved_state_version) { scoped_ptr web_contents( WebContentsState::RestoreContentsFromByteBuffer(env, clazz, state, saved_state_version, true)); if (web_contents.get()) TabAndroid::CreateHistoricalTabFromContents(web_contents.get()); } bool RegisterTabState(JNIEnv* env) { return RegisterNativesImpl(env); }