| // 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 <jni.h> |
| #include <limits> |
| #include <vector> |
| |
| #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/core/serialized_navigation_entry.h" |
| #include "components/sessions/core/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, |
| base::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<sessions::SerializedNavigationEntry>* navigations, |
| int entry_count, |
| base::PickleIterator* iterator) { |
| for (int i = 0; i < entry_count; ++i) { |
| base::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()); |
| |
| base::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<sessions::SerializedNavigationEntry>* navigations, |
| int entry_count, |
| base::PickleIterator* iterator) { |
| for (int i = 0; i < entry_count; ++i) { |
| base::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()); |
| |
| base::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<sessions::SerializedNavigationEntry>* navigations) { |
| int entry_count; |
| base::Pickle pickle(static_cast<char*>(data), size); |
| base::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. |
| } |
| base::Pickle tab_navigation_pickle(tab_navigation_data, |
| tab_navigation_data_length); |
| base::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<int>(navigations->size())) |
| return false; |
| |
| return true; |
| } |
| |
| }; // anonymous namespace |
| |
| ScopedJavaLocalRef<jobject> WebContentsState::GetContentsStateAsByteBuffer( |
| JNIEnv* env, TabAndroid* tab) { |
| Profile* profile = tab->GetProfile(); |
| if (!profile) |
| return ScopedJavaLocalRef<jobject>(); |
| |
| 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<jobject>(); |
| |
| int current_entry = controller.GetLastCommittedEntryIndex(); |
| if (current_entry == -1 && entry_count > 0) |
| current_entry = 0; |
| |
| std::vector<content::NavigationEntry*> 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<jobject> WebContentsState::WriteNavigationsAsByteBuffer( |
| JNIEnv* env, |
| bool is_off_the_record, |
| const std::vector<content::NavigationEntry*>& navigations, |
| int current_entry) { |
| base::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<jobject>(); |
| } |
| |
| // 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. |
| base::Pickle tab_navigation_pickle; |
| // Max size taken from BaseSessionService::CreateUpdateTabNavigationCommand. |
| static const size_t max_state_size = |
| std::numeric_limits<sessions::SessionCommand::size_type>::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<jobject>(); |
| } |
| // TODO(yfriedman): Add a |release| to Pickle and save the copy. |
| memcpy(buffer, pickle.data(), pickle.size()); |
| ScopedJavaLocalRef<jobject> jb(env, env->NewDirectByteBuffer(buffer, |
| pickle.size())); |
| if (base::android::ClearException(env) || jb.is_null()) |
| free(buffer); |
| return jb; |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv* env, |
| void* data, |
| int size, |
| int saved_state_version) { |
| bool is_off_the_record; |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = ExtractNavigationEntries(data, |
| size, |
| saved_state_version, |
| &is_off_the_record, |
| ¤t_entry_index, |
| &navigations); |
| if (!success) |
| return ScopedJavaLocalRef<jstring>(); |
| |
| sessions::SerializedNavigationEntry nav_entry = |
| navigations.at(current_entry_index); |
| return ConvertUTF16ToJavaString(env, nav_entry.title()); |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| WebContentsState::GetVirtualUrlFromByteBuffer(JNIEnv* env, |
| void* data, |
| int size, |
| int saved_state_version) { |
| bool is_off_the_record; |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = ExtractNavigationEntries(data, |
| size, |
| saved_state_version, |
| &is_off_the_record, |
| ¤t_entry_index, |
| &navigations); |
| if (!success) |
| return ScopedJavaLocalRef<jstring>(); |
| |
| 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<sessions::SerializedNavigationEntry> 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(); |
| std::vector<scoped_ptr<content::NavigationEntry>> entries = |
| sessions::ContentSerializedNavigationBuilder::ToNavigationEntries( |
| navigations, profile); |
| |
| if (is_off_the_record) |
| profile = profile->GetOffTheRecordProfile(); |
| WebContents::CreateParams params(profile); |
| params.initially_hidden = initially_hidden; |
| scoped_ptr<WebContents> web_contents(WebContents::Create(params)); |
| web_contents->GetController().Restore( |
| current_entry_index, NavigationController::RESTORE_CURRENT_SESSION, |
| &entries); |
| return web_contents.release(); |
| } |
| |
| ScopedJavaLocalRef<jobject> 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); |
| |
| WebContents* web_contents = WebContentsState::RestoreContentsFromByteBuffer( |
| data, |
| size, |
| saved_state_version, |
| initially_hidden); |
| |
| if (web_contents) |
| return web_contents->GetJavaWebContents(); |
| else |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| |
| ScopedJavaLocalRef<jobject> |
| 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<blink::WebReferrerPolicy>(referrer_policy)); |
| } |
| scoped_ptr<content::NavigationEntry> 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<content::NavigationEntry*> navigations(1); |
| navigations[0] = entry.get(); |
| |
| return WebContentsState::WriteNavigationsAsByteBuffer(env, |
| is_off_the_record, |
| navigations, |
| 0); |
| } |
| |
| // Static JNI methods. |
| |
| static void FreeWebContentsStateBuffer(JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jobject>& obj) { |
| void* data = env->GetDirectBufferAddress(obj); |
| free(data); |
| } |
| |
| static ScopedJavaLocalRef<jobject> RestoreContentsFromByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jobject>& state, |
| jint saved_state_version, |
| jboolean initially_hidden) { |
| return WebContentsState::RestoreContentsFromByteBuffer(env, |
| clazz, |
| state, |
| saved_state_version, |
| initially_hidden); |
| } |
| |
| static ScopedJavaLocalRef<jobject> GetContentsStateAsByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jobject>& jtab) { |
| TabAndroid* tab_android = TabAndroid::GetNativeTab(env, jtab); |
| return WebContentsState::GetContentsStateAsByteBuffer(env, tab_android); |
| } |
| |
| static ScopedJavaLocalRef<jobject> CreateSingleNavigationStateAsByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jstring>& url, |
| const JavaParamRef<jstring>& referrer_url, |
| jint referrer_policy, |
| jboolean is_off_the_record) { |
| return WebContentsState::CreateSingleNavigationStateAsByteBuffer( |
| env, url, referrer_url, referrer_policy, is_off_the_record); |
| } |
| |
| static ScopedJavaLocalRef<jstring> GetDisplayTitleFromByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jobject>& state, |
| jint saved_state_version) { |
| void* data = env->GetDirectBufferAddress(state); |
| int size = env->GetDirectBufferCapacity(state); |
| |
| ScopedJavaLocalRef<jstring> result = |
| WebContentsState::GetDisplayTitleFromByteBuffer( |
| env, data, size, saved_state_version); |
| return result; |
| } |
| |
| static ScopedJavaLocalRef<jstring> GetVirtualUrlFromByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jobject>& state, |
| jint saved_state_version) { |
| void* data = env->GetDirectBufferAddress(state); |
| int size = env->GetDirectBufferCapacity(state); |
| ScopedJavaLocalRef<jstring> result = |
| WebContentsState::GetVirtualUrlFromByteBuffer( |
| env, data, size, saved_state_version); |
| return result; |
| } |
| |
| // Creates a historical tab entry from the serialized tab contents contained |
| // within |state|. |
| static void CreateHistoricalTab(JNIEnv* env, |
| const JavaParamRef<jclass>& clazz, |
| const JavaParamRef<jobject>& state, |
| jint saved_state_version) { |
| scoped_ptr<WebContents> web_contents(WebContents::FromJavaWebContents( |
| WebContentsState::RestoreContentsFromByteBuffer(env, clazz, state, |
| saved_state_version, true) |
| .obj())); |
| if (web_contents.get()) |
| TabAndroid::CreateHistoricalTabFromContents(web_contents.get()); |
| } |
| |
| bool RegisterTabState(JNIEnv* env) { |
| return RegisterNativesImpl(env); |
| } |