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
, int entry_count
,
37 int current_entry_index
, Pickle
* pickle
) {
38 return pickle
->WriteBool(off_the_record
) &&
39 pickle
->WriteInt(entry_count
) &&
40 pickle
->WriteInt(current_entry_index
);
43 // Migrates a pickled SerializedNavigationEntry from Android tab version 0 to
44 // 2 or (Chrome 18->26).
46 // Due to the fact that all SerializedNavigationEntrys were previously stored
47 // in a single pickle on Android, this function has to read the fields exactly
48 // how they were written on m18 which is a custom format and different other
51 // This uses the fields from SerializedNavigationEntry/TabNavigation from:
52 // https://gerrit-int.chromium.org/gitweb?p=clank/internal/apps.git;
53 // a=blob;f=native/framework/chrome/tab.cc;hb=refs/heads/m18
55 // 1. For each tab navigation:
62 // 2. For each tab navigation:
64 // is_overriding_user_agent
66 void UpgradeNavigationFromV0ToV2(
67 std::vector
<sessions::SerializedNavigationEntry
>* navigations
,
69 PickleIterator
* iterator
) {
71 for (int i
= 0; i
< entry_count
; ++i
) {
73 std::string virtual_url_spec
;
74 std::string str_referrer
;
76 std::string content_state
;
77 int transition_type_int
;
78 if (!iterator
->ReadString(&virtual_url_spec
) ||
79 !iterator
->ReadString(&str_referrer
) ||
80 !iterator
->ReadString16(&title
) ||
81 !iterator
->ReadString(&content_state
) ||
82 !iterator
->ReadInt(&transition_type_int
))
85 // Write back the fields that were just read.
86 v2_pickle
.WriteInt(i
);
87 v2_pickle
.WriteString(virtual_url_spec
);
88 v2_pickle
.WriteString16(title
);
89 v2_pickle
.WriteString(content_state
);
90 v2_pickle
.WriteInt(transition_type_int
);
93 v2_pickle
.WriteInt(0);
95 v2_pickle
.WriteString(str_referrer
);
97 v2_pickle
.WriteInt(0);
98 // original_request_url_spec
99 v2_pickle
.WriteString(std::string());
100 // is_overriding_user_agent
101 v2_pickle
.WriteBool(false);
102 // timestamp_internal_value
103 v2_pickle
.WriteInt64(0);
105 v2_pickle
.WriteString16(base::string16());
107 PickleIterator
tab_navigation_pickle_iterator(v2_pickle
);
108 sessions::SerializedNavigationEntry nav
;
109 if (nav
.ReadFromPickle(&tab_navigation_pickle_iterator
)) {
110 navigations
->push_back(nav
);
112 LOG(ERROR
) << "Failed to read SerializedNavigationEntry from pickle "
113 << "(index=" << i
<< ", url=" << virtual_url_spec
;
118 for (int i
= 0; i
< entry_count
; ++i
) {
119 std::string initial_url
;
120 bool user_agent_overridden
;
121 if (!iterator
->ReadString(&initial_url
) ||
122 !iterator
->ReadBool(&user_agent_overridden
)) {
128 // Migrates a pickled SerializedNavigationEntry from Android tab version 0 to 1
129 // (or Chrome 25->26)
131 // Due to the fact that all SerializedNavigationEntrys were previously stored in
132 // a single pickle on Android, this function reads all the old fields,
133 // re-outputs them and appends an empty string16, representing the new
134 // search_terms field, and ensures that reading a v0 SerializedNavigationEntry
135 // won't consume bytes from a subsequent SerializedNavigationEntry.
137 // This uses the fields from SerializedNavigationEntry/TabNavigation prior to
138 // https://chromiumcodereview.appspot.com/11876045 which are:
147 // original_request_url
148 // is_overriding_user_agent
151 // And finally search_terms was added and this function appends it.
152 void UpgradeNavigationFromV1ToV2(
153 std::vector
<sessions::SerializedNavigationEntry
>* navigations
,
155 PickleIterator
* iterator
) {
156 for (int i
= 0; i
< entry_count
; ++i
) {
160 std::string virtual_url_spec
;
161 base::string16 title
;
162 std::string content_state
;
163 int transition_type_int
;
164 if (!iterator
->ReadInt(&index
) ||
165 !iterator
->ReadString(&virtual_url_spec
) ||
166 !iterator
->ReadString16(&title
) ||
167 !iterator
->ReadString(&content_state
) ||
168 !iterator
->ReadInt(&transition_type_int
))
171 // Write back the fields that were just read.
172 v2_pickle
.WriteInt(index
);
173 v2_pickle
.WriteString(virtual_url_spec
);
174 v2_pickle
.WriteString16(title
);
175 v2_pickle
.WriteString(content_state
);
176 v2_pickle
.WriteInt(transition_type_int
);
179 if (!iterator
->ReadInt(&type_mask
))
181 v2_pickle
.WriteInt(type_mask
);
183 std::string referrer_spec
;
184 if (iterator
->ReadString(&referrer_spec
))
185 v2_pickle
.WriteString(referrer_spec
);
188 if (iterator
->ReadInt(&policy_int
))
189 v2_pickle
.WriteInt(policy_int
);
191 std::string original_request_url_spec
;
192 if (iterator
->ReadString(&original_request_url_spec
))
193 v2_pickle
.WriteString(original_request_url_spec
);
195 bool is_overriding_user_agent
;
196 if (iterator
->ReadBool(&is_overriding_user_agent
))
197 v2_pickle
.WriteBool(is_overriding_user_agent
);
199 int64 timestamp_internal_value
= 0;
200 if (iterator
->ReadInt64(×tamp_internal_value
))
201 v2_pickle
.WriteInt64(timestamp_internal_value
);
203 // Force output of search_terms
204 v2_pickle
.WriteString16(base::string16());
206 PickleIterator
tab_navigation_pickle_iterator(v2_pickle
);
207 sessions::SerializedNavigationEntry nav
;
208 if (nav
.ReadFromPickle(&tab_navigation_pickle_iterator
)) {
209 navigations
->push_back(nav
);
211 LOG(ERROR
) << "Failed to read SerializedNavigationEntry from pickle "
212 << "(index=" << i
<< ", url=" << virtual_url_spec
;
217 // Extracts state and navigation entries from the given Pickle data and returns
218 // whether un-pickling the data succeeded
219 bool ExtractNavigationEntries(
222 int saved_state_version
,
223 bool* is_off_the_record
,
224 int* current_entry_index
,
225 std::vector
<sessions::SerializedNavigationEntry
>* navigations
) {
227 Pickle
pickle(static_cast<char*>(data
), size
);
228 PickleIterator
iter(pickle
);
229 if (!iter
.ReadBool(is_off_the_record
) || !iter
.ReadInt(&entry_count
) ||
230 !iter
.ReadInt(current_entry_index
)) {
231 LOG(ERROR
) << "Failed to restore state from byte array (length=" << size
236 if (!saved_state_version
) {
237 // When |saved_state_version| is 0, it predates our notion of each tab
238 // having a saved version id. For that version of tab serialization, we
239 // used a single pickle for all |SerializedNavigationEntry|s.
240 UpgradeNavigationFromV0ToV2(navigations
, entry_count
, &iter
);
241 } else if (saved_state_version
== 1) {
242 // When |saved_state_version| is 1, it predates our notion of each tab
243 // having a saved version id. For that version of tab serialization, we
244 // used a single pickle for all |SerializedNavigationEntry|s.
245 UpgradeNavigationFromV1ToV2(navigations
, entry_count
, &iter
);
247 // |saved_state_version| == 2 and greater.
248 for (int i
= 0; i
< entry_count
; ++i
) {
249 // Read each SerializedNavigationEntry as a separate pickle to avoid
250 // optional reads of one tab bleeding into the next tab's data.
251 int tab_navigation_data_length
= 0;
252 const char* tab_navigation_data
= NULL
;
253 if (!iter
.ReadInt(&tab_navigation_data_length
) ||
254 !iter
.ReadBytes(&tab_navigation_data
, tab_navigation_data_length
)) {
256 << "Failed to restore tab entry from byte array. "
257 << "(SerializedNavigationEntry size=" << tab_navigation_data_length
259 return false; // It's dangerous to keep deserializing now, give up.
261 Pickle
tab_navigation_pickle(tab_navigation_data
,
262 tab_navigation_data_length
);
263 PickleIterator
tab_navigation_pickle_iterator(tab_navigation_pickle
);
264 sessions::SerializedNavigationEntry nav
;
265 if (!nav
.ReadFromPickle(&tab_navigation_pickle_iterator
))
266 return false; // If we failed to read a navigation, give up on others.
268 navigations
->push_back(nav
);
272 // Validate the data.
273 if (*current_entry_index
< 0 ||
274 *current_entry_index
>= static_cast<int>(navigations
->size()))
280 }; // anonymous namespace
282 ScopedJavaLocalRef
<jobject
> WebContentsState::GetContentsStateAsByteBuffer(
283 JNIEnv
* env
, TabAndroid
* tab
) {
284 Profile
* profile
= tab
->GetProfile();
286 return ScopedJavaLocalRef
<jobject
>();
288 content::NavigationController
& controller
=
289 tab
->web_contents()->GetController();
290 const int pending_index
= controller
.GetPendingEntryIndex();
291 int entry_count
= controller
.GetEntryCount();
292 if (entry_count
== 0 && pending_index
== 0)
295 if (entry_count
== 0)
296 return ScopedJavaLocalRef
<jobject
>();
298 int current_entry
= controller
.GetLastCommittedEntryIndex();
299 if (current_entry
== -1 && entry_count
> 0)
302 std::vector
<content::NavigationEntry
*> navigations(entry_count
);
303 for (int i
= 0; i
< entry_count
; ++i
) {
304 content::NavigationEntry
* entry
= (i
== pending_index
) ?
305 controller
.GetPendingEntry() : controller
.GetEntryAtIndex(i
);
306 navigations
[i
] = entry
;
309 return WebContentsState::WriteNavigationsAsByteBuffer(
311 profile
->IsOffTheRecord(),
316 // Common implementation for GetContentsStateAsByteBuffer() and
317 // CreateContentsStateAsByteBuffer(). Does not assume ownership of the
319 ScopedJavaLocalRef
<jobject
> WebContentsState::WriteNavigationsAsByteBuffer(
321 bool is_off_the_record
,
322 const std::vector
<content::NavigationEntry
*>& navigations
,
325 if (!WriteStateHeaderToPickle(is_off_the_record
, navigations
.size(),
326 current_entry
, &pickle
)) {
327 LOG(ERROR
) << "Failed to serialize tab state (entry count=" <<
328 navigations
.size() << ").";
329 return ScopedJavaLocalRef
<jobject
>();
332 // Write out all of the NavigationEntrys.
333 for (size_t i
= 0; i
< navigations
.size(); ++i
) {
334 // Write each SerializedNavigationEntry as a separate pickle to avoid
335 // optional reads of one tab bleeding into the next tab's data.
336 Pickle tab_navigation_pickle
;
337 // Max size taken from BaseSessionService::CreateUpdateTabNavigationCommand.
338 static const size_t max_state_size
=
339 std::numeric_limits
<sessions::SessionCommand::size_type
>::max() - 1024;
340 sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
342 .WriteToPickle(max_state_size
, &tab_navigation_pickle
);
343 pickle
.WriteInt(tab_navigation_pickle
.size());
344 pickle
.WriteBytes(tab_navigation_pickle
.data(),
345 tab_navigation_pickle
.size());
348 void* buffer
= malloc(pickle
.size());
349 if (buffer
== NULL
) {
350 // We can run out of memory allocating a large enough buffer.
351 // In that case we'll only save the current entry.
352 // TODO(jcivelli): http://b/issue?id=5869635 we should save more entries.
353 // more TODO(jcivelli): Make this work
354 return ScopedJavaLocalRef
<jobject
>();
356 // TODO(yfriedman): Add a |release| to Pickle and save the copy.
357 memcpy(buffer
, pickle
.data(), pickle
.size());
358 ScopedJavaLocalRef
<jobject
> jb(env
, env
->NewDirectByteBuffer(buffer
,
360 if (base::android::ClearException(env
) || jb
.is_null())
365 ScopedJavaLocalRef
<jstring
>
366 WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv
* env
,
369 int saved_state_version
) {
370 bool is_off_the_record
;
371 int current_entry_index
;
372 std::vector
<sessions::SerializedNavigationEntry
> navigations
;
373 bool success
= ExtractNavigationEntries(data
,
377 ¤t_entry_index
,
380 return ScopedJavaLocalRef
<jstring
>();
382 sessions::SerializedNavigationEntry nav_entry
=
383 navigations
.at(current_entry_index
);
384 return ConvertUTF16ToJavaString(env
, nav_entry
.title());
387 ScopedJavaLocalRef
<jstring
>
388 WebContentsState::GetVirtualUrlFromByteBuffer(JNIEnv
* env
,
391 int saved_state_version
) {
392 bool is_off_the_record
;
393 int current_entry_index
;
394 std::vector
<sessions::SerializedNavigationEntry
> navigations
;
395 bool success
= ExtractNavigationEntries(data
,
399 ¤t_entry_index
,
402 return ScopedJavaLocalRef
<jstring
>();
404 sessions::SerializedNavigationEntry nav_entry
=
405 navigations
.at(current_entry_index
);
406 return ConvertUTF8ToJavaString(env
, nav_entry
.virtual_url().spec());
409 WebContents
* WebContentsState::RestoreContentsFromByteBuffer(
412 int saved_state_version
,
413 bool initially_hidden
) {
414 bool is_off_the_record
;
415 int current_entry_index
;
416 std::vector
<sessions::SerializedNavigationEntry
> navigations
;
417 bool success
= ExtractNavigationEntries(data
,
421 ¤t_entry_index
,
426 Profile
* profile
= ProfileManager::GetActiveUserProfile();
427 ScopedVector
<content::NavigationEntry
> scoped_entries
=
428 sessions::ContentSerializedNavigationBuilder::ToNavigationEntries(
429 navigations
, profile
);
430 std::vector
<content::NavigationEntry
*> entries
;
431 scoped_entries
.release(&entries
);
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 WebContents
* WebContentsState::RestoreContentsFromByteBuffer(
449 jint saved_state_version
,
450 jboolean initially_hidden
) {
451 void* data
= env
->GetDirectBufferAddress(state
);
452 int size
= env
->GetDirectBufferCapacity(state
);
454 return WebContentsState::RestoreContentsFromByteBuffer(data
,
460 ScopedJavaLocalRef
<jobject
>
461 WebContentsState::CreateSingleNavigationStateAsByteBuffer(
464 jstring referrer_url
,
465 jint referrer_policy
,
466 jboolean is_off_the_record
) {
467 content::Referrer referrer
;
469 referrer
= content::Referrer(
470 GURL(base::android::ConvertJavaStringToUTF8(env
, referrer_url
)),
471 static_cast<blink::WebReferrerPolicy
>(referrer_policy
));
473 scoped_ptr
<content::NavigationEntry
> entry(
474 content::NavigationController::CreateNavigationEntry(
475 GURL(base::android::ConvertJavaStringToUTF8(env
, url
)),
477 ui::PAGE_TRANSITION_LINK
,
478 true, // is_renderer_initiated
480 ProfileManager::GetActiveUserProfile()));
482 std::vector
<content::NavigationEntry
*> navigations(1);
483 navigations
[0] = entry
.get();
485 return WebContentsState::WriteNavigationsAsByteBuffer(env
,
491 // Static JNI methods.
493 static void FreeWebContentsStateBuffer(JNIEnv
* env
, jclass clazz
, jobject obj
) {
494 void* data
= env
->GetDirectBufferAddress(obj
);
498 static jlong
RestoreContentsFromByteBuffer(JNIEnv
* env
,
501 jint saved_state_version
,
502 jboolean initially_hidden
) {
503 return reinterpret_cast<intptr_t>(
504 WebContentsState::RestoreContentsFromByteBuffer(env
,
511 static jobject
GetContentsStateAsByteBuffer(
512 JNIEnv
* env
, jclass clazz
, jobject jtab
) {
513 TabAndroid
* tab_android
= TabAndroid::GetNativeTab(env
, jtab
);
514 return WebContentsState::GetContentsStateAsByteBuffer(
515 env
, tab_android
).Release();
518 static jobject
CreateSingleNavigationStateAsByteBuffer(
522 jstring referrer_url
,
523 jint referrer_policy
,
524 jboolean is_off_the_record
) {
525 return WebContentsState::CreateSingleNavigationStateAsByteBuffer(
526 env
, url
, referrer_url
, referrer_policy
, is_off_the_record
).Release();
529 static jstring
GetDisplayTitleFromByteBuffer(JNIEnv
* env
,
532 jint saved_state_version
) {
533 void* data
= env
->GetDirectBufferAddress(state
);
534 int size
= env
->GetDirectBufferCapacity(state
);
536 ScopedJavaLocalRef
<jstring
> result
=
537 WebContentsState::GetDisplayTitleFromByteBuffer(
538 env
, data
, size
, saved_state_version
);
539 return result
.Release();
542 static jstring
GetVirtualUrlFromByteBuffer(JNIEnv
* env
,
545 jint saved_state_version
) {
546 void* data
= env
->GetDirectBufferAddress(state
);
547 int size
= env
->GetDirectBufferCapacity(state
);
548 ScopedJavaLocalRef
<jstring
> result
=
549 WebContentsState::GetVirtualUrlFromByteBuffer(
550 env
, data
, size
, saved_state_version
);
551 return result
.Release();
554 // Creates a historical tab entry from the serialized tab contents contained
556 static void CreateHistoricalTab(JNIEnv
* env
,
559 jint saved_state_version
) {
560 scoped_ptr
<WebContents
> web_contents(
561 WebContentsState::RestoreContentsFromByteBuffer(env
,
566 if (web_contents
.get())
567 TabAndroid::CreateHistoricalTabFromContents(web_contents
.get());
570 bool RegisterTabState(JNIEnv
* env
) {
571 return RegisterNativesImpl(env
);