Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / android / tab_state.cc
blob48962aedc66bf8717bdfd0659e8e3d5a4e72e39b
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/android/tab_state.h"
7 #include <jni.h>
8 #include <limits>
9 #include <vector>
11 #include "base/android/jni_android.h"
12 #include "base/android/jni_string.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/memory/scoped_vector.h"
16 #include "base/pickle.h"
17 #include "chrome/browser/android/tab_android.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/profiles/profile_manager.h"
20 #include "components/sessions/content/content_serialized_navigation_builder.h"
21 #include "components/sessions/serialized_navigation_entry.h"
22 #include "components/sessions/session_command.h"
23 #include "content/public/browser/navigation_controller.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/web_contents.h"
26 #include "jni/TabState_jni.h"
28 using base::android::ConvertUTF16ToJavaString;
29 using base::android::ConvertUTF8ToJavaString;
30 using base::android::ScopedJavaLocalRef;
31 using content::NavigationController;
32 using content::WebContents;
34 namespace {
36 bool WriteStateHeaderToPickle(bool off_the_record,
37 int entry_count,
38 int current_entry_index,
39 base::Pickle* pickle) {
40 return pickle->WriteBool(off_the_record) &&
41 pickle->WriteInt(entry_count) &&
42 pickle->WriteInt(current_entry_index);
45 // Migrates a pickled SerializedNavigationEntry from Android tab version 0 to
46 // 2 or (Chrome 18->26).
48 // Due to the fact that all SerializedNavigationEntrys were previously stored
49 // in a single pickle on Android, this function has to read the fields exactly
50 // how they were written on m18 which is a custom format and different other
51 // chromes.
53 // This uses the fields from SerializedNavigationEntry/TabNavigation from:
54 // https://gerrit-int.chromium.org/gitweb?p=clank/internal/apps.git;
55 // a=blob;f=native/framework/chrome/tab.cc;hb=refs/heads/m18
57 // 1. For each tab navigation:
58 // virtual_url
59 // title
60 // content_state
61 // transition_type
62 // type_mask
64 // 2. For each tab navigation:
65 // referrer
66 // is_overriding_user_agent
68 void UpgradeNavigationFromV0ToV2(
69 std::vector<sessions::SerializedNavigationEntry>* navigations,
70 int entry_count,
71 base::PickleIterator* iterator) {
72 for (int i = 0; i < entry_count; ++i) {
73 base::Pickle v2_pickle;
74 std::string virtual_url_spec;
75 std::string str_referrer;
76 base::string16 title;
77 std::string content_state;
78 int transition_type_int;
79 if (!iterator->ReadString(&virtual_url_spec) ||
80 !iterator->ReadString(&str_referrer) ||
81 !iterator->ReadString16(&title) ||
82 !iterator->ReadString(&content_state) ||
83 !iterator->ReadInt(&transition_type_int))
84 return;
86 // Write back the fields that were just read.
87 v2_pickle.WriteInt(i);
88 v2_pickle.WriteString(virtual_url_spec);
89 v2_pickle.WriteString16(title);
90 v2_pickle.WriteString(content_state);
91 v2_pickle.WriteInt(transition_type_int);
93 // type_mask
94 v2_pickle.WriteInt(0);
95 // referrer_spec
96 v2_pickle.WriteString(str_referrer);
97 // policy_int
98 v2_pickle.WriteInt(0);
99 // original_request_url_spec
100 v2_pickle.WriteString(std::string());
101 // is_overriding_user_agent
102 v2_pickle.WriteBool(false);
103 // timestamp_internal_value
104 v2_pickle.WriteInt64(0);
105 // search_terms
106 v2_pickle.WriteString16(base::string16());
108 base::PickleIterator tab_navigation_pickle_iterator(v2_pickle);
109 sessions::SerializedNavigationEntry nav;
110 if (nav.ReadFromPickle(&tab_navigation_pickle_iterator)) {
111 navigations->push_back(nav);
112 } else {
113 LOG(ERROR) << "Failed to read SerializedNavigationEntry from pickle "
114 << "(index=" << i << ", url=" << virtual_url_spec;
119 for (int i = 0; i < entry_count; ++i) {
120 std::string initial_url;
121 bool user_agent_overridden;
122 if (!iterator->ReadString(&initial_url) ||
123 !iterator->ReadBool(&user_agent_overridden)) {
124 break;
129 // Migrates a pickled SerializedNavigationEntry from Android tab version 0 to 1
130 // (or Chrome 25->26)
132 // Due to the fact that all SerializedNavigationEntrys were previously stored in
133 // a single pickle on Android, this function reads all the old fields,
134 // re-outputs them and appends an empty string16, representing the new
135 // search_terms field, and ensures that reading a v0 SerializedNavigationEntry
136 // won't consume bytes from a subsequent SerializedNavigationEntry.
138 // This uses the fields from SerializedNavigationEntry/TabNavigation prior to
139 // https://chromiumcodereview.appspot.com/11876045 which are:
141 // index
142 // virtual_url
143 // title
144 // content_state
145 // transition_type
146 // type_mask
147 // referrer
148 // original_request_url
149 // is_overriding_user_agent
150 // timestamp
152 // And finally search_terms was added and this function appends it.
153 void UpgradeNavigationFromV1ToV2(
154 std::vector<sessions::SerializedNavigationEntry>* navigations,
155 int entry_count,
156 base::PickleIterator* iterator) {
157 for (int i = 0; i < entry_count; ++i) {
158 base::Pickle v2_pickle;
160 int index;
161 std::string virtual_url_spec;
162 base::string16 title;
163 std::string content_state;
164 int transition_type_int;
165 if (!iterator->ReadInt(&index) ||
166 !iterator->ReadString(&virtual_url_spec) ||
167 !iterator->ReadString16(&title) ||
168 !iterator->ReadString(&content_state) ||
169 !iterator->ReadInt(&transition_type_int))
170 return;
172 // Write back the fields that were just read.
173 v2_pickle.WriteInt(index);
174 v2_pickle.WriteString(virtual_url_spec);
175 v2_pickle.WriteString16(title);
176 v2_pickle.WriteString(content_state);
177 v2_pickle.WriteInt(transition_type_int);
179 int type_mask = 0;
180 if (!iterator->ReadInt(&type_mask))
181 continue;
182 v2_pickle.WriteInt(type_mask);
184 std::string referrer_spec;
185 if (iterator->ReadString(&referrer_spec))
186 v2_pickle.WriteString(referrer_spec);
188 int policy_int;
189 if (iterator->ReadInt(&policy_int))
190 v2_pickle.WriteInt(policy_int);
192 std::string original_request_url_spec;
193 if (iterator->ReadString(&original_request_url_spec))
194 v2_pickle.WriteString(original_request_url_spec);
196 bool is_overriding_user_agent;
197 if (iterator->ReadBool(&is_overriding_user_agent))
198 v2_pickle.WriteBool(is_overriding_user_agent);
200 int64 timestamp_internal_value = 0;
201 if (iterator->ReadInt64(&timestamp_internal_value))
202 v2_pickle.WriteInt64(timestamp_internal_value);
204 // Force output of search_terms
205 v2_pickle.WriteString16(base::string16());
207 base::PickleIterator tab_navigation_pickle_iterator(v2_pickle);
208 sessions::SerializedNavigationEntry nav;
209 if (nav.ReadFromPickle(&tab_navigation_pickle_iterator)) {
210 navigations->push_back(nav);
211 } else {
212 LOG(ERROR) << "Failed to read SerializedNavigationEntry from pickle "
213 << "(index=" << i << ", url=" << virtual_url_spec;
218 // Extracts state and navigation entries from the given Pickle data and returns
219 // whether un-pickling the data succeeded
220 bool ExtractNavigationEntries(
221 void* data,
222 int size,
223 int saved_state_version,
224 bool* is_off_the_record,
225 int* current_entry_index,
226 std::vector<sessions::SerializedNavigationEntry>* navigations) {
227 int entry_count;
228 base::Pickle pickle(static_cast<char*>(data), size);
229 base::PickleIterator iter(pickle);
230 if (!iter.ReadBool(is_off_the_record) || !iter.ReadInt(&entry_count) ||
231 !iter.ReadInt(current_entry_index)) {
232 LOG(ERROR) << "Failed to restore state from byte array (length=" << size
233 << ").";
234 return false;
237 if (!saved_state_version) {
238 // When |saved_state_version| is 0, it predates our notion of each tab
239 // having a saved version id. For that version of tab serialization, we
240 // used a single pickle for all |SerializedNavigationEntry|s.
241 UpgradeNavigationFromV0ToV2(navigations, entry_count, &iter);
242 } else if (saved_state_version == 1) {
243 // When |saved_state_version| is 1, it predates our notion of each tab
244 // having a saved version id. For that version of tab serialization, we
245 // used a single pickle for all |SerializedNavigationEntry|s.
246 UpgradeNavigationFromV1ToV2(navigations, entry_count, &iter);
247 } else {
248 // |saved_state_version| == 2 and greater.
249 for (int i = 0; i < entry_count; ++i) {
250 // Read each SerializedNavigationEntry as a separate pickle to avoid
251 // optional reads of one tab bleeding into the next tab's data.
252 int tab_navigation_data_length = 0;
253 const char* tab_navigation_data = NULL;
254 if (!iter.ReadInt(&tab_navigation_data_length) ||
255 !iter.ReadBytes(&tab_navigation_data, tab_navigation_data_length)) {
256 LOG(ERROR)
257 << "Failed to restore tab entry from byte array. "
258 << "(SerializedNavigationEntry size=" << tab_navigation_data_length
259 << ").";
260 return false; // It's dangerous to keep deserializing now, give up.
262 base::Pickle tab_navigation_pickle(tab_navigation_data,
263 tab_navigation_data_length);
264 base::PickleIterator tab_navigation_pickle_iterator(
265 tab_navigation_pickle);
266 sessions::SerializedNavigationEntry nav;
267 if (!nav.ReadFromPickle(&tab_navigation_pickle_iterator))
268 return false; // If we failed to read a navigation, give up on others.
270 navigations->push_back(nav);
274 // Validate the data.
275 if (*current_entry_index < 0 ||
276 *current_entry_index >= static_cast<int>(navigations->size()))
277 return false;
279 return true;
282 }; // anonymous namespace
284 ScopedJavaLocalRef<jobject> WebContentsState::GetContentsStateAsByteBuffer(
285 JNIEnv* env, TabAndroid* tab) {
286 Profile* profile = tab->GetProfile();
287 if (!profile)
288 return ScopedJavaLocalRef<jobject>();
290 content::NavigationController& controller =
291 tab->web_contents()->GetController();
292 const int pending_index = controller.GetPendingEntryIndex();
293 int entry_count = controller.GetEntryCount();
294 if (entry_count == 0 && pending_index == 0)
295 entry_count++;
297 if (entry_count == 0)
298 return ScopedJavaLocalRef<jobject>();
300 int current_entry = controller.GetLastCommittedEntryIndex();
301 if (current_entry == -1 && entry_count > 0)
302 current_entry = 0;
304 std::vector<content::NavigationEntry*> navigations(entry_count);
305 for (int i = 0; i < entry_count; ++i) {
306 content::NavigationEntry* entry = (i == pending_index) ?
307 controller.GetPendingEntry() : controller.GetEntryAtIndex(i);
308 navigations[i] = entry;
311 return WebContentsState::WriteNavigationsAsByteBuffer(
312 env,
313 profile->IsOffTheRecord(),
314 navigations,
315 current_entry);
318 // Common implementation for GetContentsStateAsByteBuffer() and
319 // CreateContentsStateAsByteBuffer(). Does not assume ownership of the
320 // navigations.
321 ScopedJavaLocalRef<jobject> WebContentsState::WriteNavigationsAsByteBuffer(
322 JNIEnv* env,
323 bool is_off_the_record,
324 const std::vector<content::NavigationEntry*>& navigations,
325 int current_entry) {
326 base::Pickle pickle;
327 if (!WriteStateHeaderToPickle(is_off_the_record, navigations.size(),
328 current_entry, &pickle)) {
329 LOG(ERROR) << "Failed to serialize tab state (entry count=" <<
330 navigations.size() << ").";
331 return ScopedJavaLocalRef<jobject>();
334 // Write out all of the NavigationEntrys.
335 for (size_t i = 0; i < navigations.size(); ++i) {
336 // Write each SerializedNavigationEntry as a separate pickle to avoid
337 // optional reads of one tab bleeding into the next tab's data.
338 base::Pickle tab_navigation_pickle;
339 // Max size taken from BaseSessionService::CreateUpdateTabNavigationCommand.
340 static const size_t max_state_size =
341 std::numeric_limits<sessions::SessionCommand::size_type>::max() - 1024;
342 sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
343 i, *navigations[i])
344 .WriteToPickle(max_state_size, &tab_navigation_pickle);
345 pickle.WriteInt(tab_navigation_pickle.size());
346 pickle.WriteBytes(tab_navigation_pickle.data(),
347 tab_navigation_pickle.size());
350 void* buffer = malloc(pickle.size());
351 if (buffer == NULL) {
352 // We can run out of memory allocating a large enough buffer.
353 // In that case we'll only save the current entry.
354 // TODO(jcivelli): http://b/issue?id=5869635 we should save more entries.
355 // more TODO(jcivelli): Make this work
356 return ScopedJavaLocalRef<jobject>();
358 // TODO(yfriedman): Add a |release| to Pickle and save the copy.
359 memcpy(buffer, pickle.data(), pickle.size());
360 ScopedJavaLocalRef<jobject> jb(env, env->NewDirectByteBuffer(buffer,
361 pickle.size()));
362 if (base::android::ClearException(env) || jb.is_null())
363 free(buffer);
364 return jb;
367 ScopedJavaLocalRef<jstring>
368 WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv* env,
369 void* data,
370 int size,
371 int saved_state_version) {
372 bool is_off_the_record;
373 int current_entry_index;
374 std::vector<sessions::SerializedNavigationEntry> navigations;
375 bool success = ExtractNavigationEntries(data,
376 size,
377 saved_state_version,
378 &is_off_the_record,
379 &current_entry_index,
380 &navigations);
381 if (!success)
382 return ScopedJavaLocalRef<jstring>();
384 sessions::SerializedNavigationEntry nav_entry =
385 navigations.at(current_entry_index);
386 return ConvertUTF16ToJavaString(env, nav_entry.title());
389 ScopedJavaLocalRef<jstring>
390 WebContentsState::GetVirtualUrlFromByteBuffer(JNIEnv* env,
391 void* data,
392 int size,
393 int saved_state_version) {
394 bool is_off_the_record;
395 int current_entry_index;
396 std::vector<sessions::SerializedNavigationEntry> navigations;
397 bool success = ExtractNavigationEntries(data,
398 size,
399 saved_state_version,
400 &is_off_the_record,
401 &current_entry_index,
402 &navigations);
403 if (!success)
404 return ScopedJavaLocalRef<jstring>();
406 sessions::SerializedNavigationEntry nav_entry =
407 navigations.at(current_entry_index);
408 return ConvertUTF8ToJavaString(env, nav_entry.virtual_url().spec());
411 WebContents* WebContentsState::RestoreContentsFromByteBuffer(
412 void* data,
413 int size,
414 int saved_state_version,
415 bool initially_hidden) {
416 bool is_off_the_record;
417 int current_entry_index;
418 std::vector<sessions::SerializedNavigationEntry> navigations;
419 bool success = ExtractNavigationEntries(data,
420 size,
421 saved_state_version,
422 &is_off_the_record,
423 &current_entry_index,
424 &navigations);
425 if (!success)
426 return NULL;
428 Profile* profile = ProfileManager::GetActiveUserProfile();
429 ScopedVector<content::NavigationEntry> scoped_entries =
430 sessions::ContentSerializedNavigationBuilder::ToNavigationEntries(
431 navigations, profile);
433 if (is_off_the_record)
434 profile = profile->GetOffTheRecordProfile();
435 WebContents::CreateParams params(profile);
436 params.initially_hidden = initially_hidden;
437 scoped_ptr<WebContents> web_contents(WebContents::Create(params));
438 web_contents->GetController().Restore(
439 current_entry_index,
440 NavigationController::RESTORE_CURRENT_SESSION,
441 &scoped_entries);
442 return web_contents.release();
445 ScopedJavaLocalRef<jobject> WebContentsState::RestoreContentsFromByteBuffer(
446 JNIEnv* env,
447 jclass clazz,
448 jobject state,
449 jint saved_state_version,
450 jboolean initially_hidden) {
451 void* data = env->GetDirectBufferAddress(state);
452 int size = env->GetDirectBufferCapacity(state);
454 WebContents* web_contents = WebContentsState::RestoreContentsFromByteBuffer(
455 data,
456 size,
457 saved_state_version,
458 initially_hidden);
460 if (web_contents)
461 return web_contents->GetJavaWebContents();
462 else
463 return ScopedJavaLocalRef<jobject>();
466 ScopedJavaLocalRef<jobject>
467 WebContentsState::CreateSingleNavigationStateAsByteBuffer(
468 JNIEnv* env,
469 jstring url,
470 jstring referrer_url,
471 jint referrer_policy,
472 jboolean is_off_the_record) {
473 content::Referrer referrer;
474 if (referrer_url) {
475 referrer = content::Referrer(
476 GURL(base::android::ConvertJavaStringToUTF8(env, referrer_url)),
477 static_cast<blink::WebReferrerPolicy>(referrer_policy));
479 scoped_ptr<content::NavigationEntry> entry(
480 content::NavigationController::CreateNavigationEntry(
481 GURL(base::android::ConvertJavaStringToUTF8(env, url)),
482 referrer,
483 ui::PAGE_TRANSITION_LINK,
484 true, // is_renderer_initiated
485 "", // extra_headers
486 ProfileManager::GetActiveUserProfile()));
488 std::vector<content::NavigationEntry*> navigations(1);
489 navigations[0] = entry.get();
491 return WebContentsState::WriteNavigationsAsByteBuffer(env,
492 is_off_the_record,
493 navigations,
497 // Static JNI methods.
499 static void FreeWebContentsStateBuffer(JNIEnv* env,
500 const JavaParamRef<jclass>& clazz,
501 const JavaParamRef<jobject>& obj) {
502 void* data = env->GetDirectBufferAddress(obj);
503 free(data);
506 static ScopedJavaLocalRef<jobject> RestoreContentsFromByteBuffer(
507 JNIEnv* env,
508 const JavaParamRef<jclass>& clazz,
509 const JavaParamRef<jobject>& state,
510 jint saved_state_version,
511 jboolean initially_hidden) {
512 return WebContentsState::RestoreContentsFromByteBuffer(env,
513 clazz,
514 state,
515 saved_state_version,
516 initially_hidden);
519 static ScopedJavaLocalRef<jobject> GetContentsStateAsByteBuffer(
520 JNIEnv* env,
521 const JavaParamRef<jclass>& clazz,
522 const JavaParamRef<jobject>& jtab) {
523 TabAndroid* tab_android = TabAndroid::GetNativeTab(env, jtab);
524 return WebContentsState::GetContentsStateAsByteBuffer(env, tab_android);
527 static ScopedJavaLocalRef<jobject> CreateSingleNavigationStateAsByteBuffer(
528 JNIEnv* env,
529 const JavaParamRef<jclass>& clazz,
530 const JavaParamRef<jstring>& url,
531 const JavaParamRef<jstring>& referrer_url,
532 jint referrer_policy,
533 jboolean is_off_the_record) {
534 return WebContentsState::CreateSingleNavigationStateAsByteBuffer(
535 env, url, referrer_url, referrer_policy, is_off_the_record);
538 static ScopedJavaLocalRef<jstring> GetDisplayTitleFromByteBuffer(
539 JNIEnv* env,
540 const JavaParamRef<jclass>& clazz,
541 const JavaParamRef<jobject>& state,
542 jint saved_state_version) {
543 void* data = env->GetDirectBufferAddress(state);
544 int size = env->GetDirectBufferCapacity(state);
546 ScopedJavaLocalRef<jstring> result =
547 WebContentsState::GetDisplayTitleFromByteBuffer(
548 env, data, size, saved_state_version);
549 return result;
552 static ScopedJavaLocalRef<jstring> GetVirtualUrlFromByteBuffer(
553 JNIEnv* env,
554 const JavaParamRef<jclass>& clazz,
555 const JavaParamRef<jobject>& state,
556 jint saved_state_version) {
557 void* data = env->GetDirectBufferAddress(state);
558 int size = env->GetDirectBufferCapacity(state);
559 ScopedJavaLocalRef<jstring> result =
560 WebContentsState::GetVirtualUrlFromByteBuffer(
561 env, data, size, saved_state_version);
562 return result;
565 // Creates a historical tab entry from the serialized tab contents contained
566 // within |state|.
567 static void CreateHistoricalTab(JNIEnv* env,
568 const JavaParamRef<jclass>& clazz,
569 const JavaParamRef<jobject>& state,
570 jint saved_state_version) {
571 scoped_ptr<WebContents> web_contents(WebContents::FromJavaWebContents(
572 WebContentsState::RestoreContentsFromByteBuffer(env, clazz, state,
573 saved_state_version, true)
574 .obj()));
575 if (web_contents.get())
576 TabAndroid::CreateHistoricalTabFromContents(web_contents.get());
579 bool RegisterTabState(JNIEnv* env) {
580 return RegisterNativesImpl(env);