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"
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
;
36 bool WriteStateHeaderToPickle(bool off_the_record
,
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
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:
64 // 2. For each tab navigation:
66 // is_overriding_user_agent
68 void UpgradeNavigationFromV0ToV2(
69 std::vector
<sessions::SerializedNavigationEntry
>* navigations
,
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
;
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
))
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
);
94 v2_pickle
.WriteInt(0);
96 v2_pickle
.WriteString(str_referrer
);
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);
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
);
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
)) {
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:
148 // original_request_url
149 // is_overriding_user_agent
152 // And finally search_terms was added and this function appends it.
153 void UpgradeNavigationFromV1ToV2(
154 std::vector
<sessions::SerializedNavigationEntry
>* navigations
,
156 base::PickleIterator
* iterator
) {
157 for (int i
= 0; i
< entry_count
; ++i
) {
158 base::Pickle v2_pickle
;
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
))
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
);
180 if (!iterator
->ReadInt(&type_mask
))
182 v2_pickle
.WriteInt(type_mask
);
184 std::string referrer_spec
;
185 if (iterator
->ReadString(&referrer_spec
))
186 v2_pickle
.WriteString(referrer_spec
);
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(×tamp_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
);
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(
223 int saved_state_version
,
224 bool* is_off_the_record
,
225 int* current_entry_index
,
226 std::vector
<sessions::SerializedNavigationEntry
>* navigations
) {
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
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
);
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
)) {
257 << "Failed to restore tab entry from byte array. "
258 << "(SerializedNavigationEntry size=" << tab_navigation_data_length
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()))
282 }; // anonymous namespace
284 ScopedJavaLocalRef
<jobject
> WebContentsState::GetContentsStateAsByteBuffer(
285 JNIEnv
* env
, TabAndroid
* tab
) {
286 Profile
* profile
= tab
->GetProfile();
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)
297 if (entry_count
== 0)
298 return ScopedJavaLocalRef
<jobject
>();
300 int current_entry
= controller
.GetLastCommittedEntryIndex();
301 if (current_entry
== -1 && entry_count
> 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(
313 profile
->IsOffTheRecord(),
318 // Common implementation for GetContentsStateAsByteBuffer() and
319 // CreateContentsStateAsByteBuffer(). Does not assume ownership of the
321 ScopedJavaLocalRef
<jobject
> WebContentsState::WriteNavigationsAsByteBuffer(
323 bool is_off_the_record
,
324 const std::vector
<content::NavigationEntry
*>& navigations
,
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(
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
,
362 if (base::android::ClearException(env
) || jb
.is_null())
367 ScopedJavaLocalRef
<jstring
>
368 WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv
* env
,
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
,
379 ¤t_entry_index
,
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
,
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
,
401 ¤t_entry_index
,
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(
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
,
423 ¤t_entry_index
,
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(
440 NavigationController::RESTORE_CURRENT_SESSION
,
442 return web_contents
.release();
445 ScopedJavaLocalRef
<jobject
> WebContentsState::RestoreContentsFromByteBuffer(
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(
461 return web_contents
->GetJavaWebContents();
463 return ScopedJavaLocalRef
<jobject
>();
466 ScopedJavaLocalRef
<jobject
>
467 WebContentsState::CreateSingleNavigationStateAsByteBuffer(
470 jstring referrer_url
,
471 jint referrer_policy
,
472 jboolean is_off_the_record
) {
473 content::Referrer referrer
;
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
)),
483 ui::PAGE_TRANSITION_LINK
,
484 true, // is_renderer_initiated
486 ProfileManager::GetActiveUserProfile()));
488 std::vector
<content::NavigationEntry
*> navigations(1);
489 navigations
[0] = entry
.get();
491 return WebContentsState::WriteNavigationsAsByteBuffer(env
,
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
);
506 static ScopedJavaLocalRef
<jobject
> RestoreContentsFromByteBuffer(
508 const JavaParamRef
<jclass
>& clazz
,
509 const JavaParamRef
<jobject
>& state
,
510 jint saved_state_version
,
511 jboolean initially_hidden
) {
512 return WebContentsState::RestoreContentsFromByteBuffer(env
,
519 static ScopedJavaLocalRef
<jobject
> GetContentsStateAsByteBuffer(
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(
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(
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
);
552 static ScopedJavaLocalRef
<jstring
> GetVirtualUrlFromByteBuffer(
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
);
565 // Creates a historical tab entry from the serialized tab contents contained
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)
575 if (web_contents
.get())
576 TabAndroid::CreateHistoricalTabFromContents(web_contents
.get());
579 bool RegisterTabState(JNIEnv
* env
) {
580 return RegisterNativesImpl(env
);