Refactor SharedMemory::Create and fix a rare file leak.
[chromium-blink-merge.git] / chrome / browser / android / tab_state.cc
blob5e01566491aedfdac4cbd9d44756bee0e7dc70e1
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, 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
49 // chromes.
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:
56 // virtual_url
57 // title
58 // content_state
59 // transition_type
60 // type_mask
62 // 2. For each tab navigation:
63 // referrer
64 // is_overriding_user_agent
66 void UpgradeNavigationFromV0ToV2(
67 std::vector<sessions::SerializedNavigationEntry>* navigations,
68 int entry_count,
69 PickleIterator* iterator) {
71 for (int i = 0; i < entry_count; ++i) {
72 Pickle v2_pickle;
73 std::string virtual_url_spec;
74 std::string str_referrer;
75 base::string16 title;
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))
83 return;
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);
92 // type_mask
93 v2_pickle.WriteInt(0);
94 // referrer_spec
95 v2_pickle.WriteString(str_referrer);
96 // policy_int
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);
104 // search_terms
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);
111 } else {
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)) {
123 break;
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:
140 // index
141 // virtual_url
142 // title
143 // content_state
144 // transition_type
145 // type_mask
146 // referrer
147 // original_request_url
148 // is_overriding_user_agent
149 // timestamp
151 // And finally search_terms was added and this function appends it.
152 void UpgradeNavigationFromV1ToV2(
153 std::vector<sessions::SerializedNavigationEntry>* navigations,
154 int entry_count,
155 PickleIterator* iterator) {
156 for (int i = 0; i < entry_count; ++i) {
157 Pickle v2_pickle;
159 int index;
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))
169 return;
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);
178 int type_mask = 0;
179 if (!iterator->ReadInt(&type_mask))
180 continue;
181 v2_pickle.WriteInt(type_mask);
183 std::string referrer_spec;
184 if (iterator->ReadString(&referrer_spec))
185 v2_pickle.WriteString(referrer_spec);
187 int policy_int;
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(&timestamp_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);
210 } else {
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(
220 void* data,
221 int size,
222 int saved_state_version,
223 bool* is_off_the_record,
224 int* current_entry_index,
225 std::vector<sessions::SerializedNavigationEntry>* navigations) {
226 int entry_count;
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
232 << ").";
233 return false;
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);
246 } else {
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)) {
255 LOG(ERROR)
256 << "Failed to restore tab entry from byte array. "
257 << "(SerializedNavigationEntry size=" << tab_navigation_data_length
258 << ").";
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()))
275 return false;
277 return true;
280 }; // anonymous namespace
282 ScopedJavaLocalRef<jobject> WebContentsState::GetContentsStateAsByteBuffer(
283 JNIEnv* env, TabAndroid* tab) {
284 Profile* profile = tab->GetProfile();
285 if (!profile)
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)
293 entry_count++;
295 if (entry_count == 0)
296 return ScopedJavaLocalRef<jobject>();
298 int current_entry = controller.GetLastCommittedEntryIndex();
299 if (current_entry == -1 && entry_count > 0)
300 current_entry = 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(
310 env,
311 profile->IsOffTheRecord(),
312 navigations,
313 current_entry);
316 // Common implementation for GetContentsStateAsByteBuffer() and
317 // CreateContentsStateAsByteBuffer(). Does not assume ownership of the
318 // navigations.
319 ScopedJavaLocalRef<jobject> WebContentsState::WriteNavigationsAsByteBuffer(
320 JNIEnv* env,
321 bool is_off_the_record,
322 const std::vector<content::NavigationEntry*>& navigations,
323 int current_entry) {
324 Pickle pickle;
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(
341 i, *navigations[i])
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,
359 pickle.size()));
360 if (base::android::ClearException(env) || jb.is_null())
361 free(buffer);
362 return jb;
365 ScopedJavaLocalRef<jstring>
366 WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv* env,
367 void* data,
368 int size,
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,
374 size,
375 saved_state_version,
376 &is_off_the_record,
377 &current_entry_index,
378 &navigations);
379 if (!success)
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,
389 void* data,
390 int size,
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,
396 size,
397 saved_state_version,
398 &is_off_the_record,
399 &current_entry_index,
400 &navigations);
401 if (!success)
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(
410 void* data,
411 int size,
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,
418 size,
419 saved_state_version,
420 &is_off_the_record,
421 &current_entry_index,
422 &navigations);
423 if (!success)
424 return NULL;
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(
439 current_entry_index,
440 NavigationController::RESTORE_CURRENT_SESSION,
441 &entries);
442 return web_contents.release();
445 WebContents* 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 return WebContentsState::RestoreContentsFromByteBuffer(data,
455 size,
456 saved_state_version,
457 initially_hidden);
460 ScopedJavaLocalRef<jobject>
461 WebContentsState::CreateSingleNavigationStateAsByteBuffer(
462 JNIEnv* env,
463 jstring url,
464 jstring referrer_url,
465 jint referrer_policy,
466 jboolean is_off_the_record) {
467 content::Referrer referrer;
468 if (referrer_url) {
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)),
476 referrer,
477 ui::PAGE_TRANSITION_LINK,
478 true, // is_renderer_initiated
479 "", // extra_headers
480 ProfileManager::GetActiveUserProfile()));
482 std::vector<content::NavigationEntry*> navigations(1);
483 navigations[0] = entry.get();
485 return WebContentsState::WriteNavigationsAsByteBuffer(env,
486 is_off_the_record,
487 navigations,
491 // Static JNI methods.
493 static void FreeWebContentsStateBuffer(JNIEnv* env, jclass clazz, jobject obj) {
494 void* data = env->GetDirectBufferAddress(obj);
495 free(data);
498 static jlong RestoreContentsFromByteBuffer(JNIEnv* env,
499 jclass clazz,
500 jobject state,
501 jint saved_state_version,
502 jboolean initially_hidden) {
503 return reinterpret_cast<intptr_t>(
504 WebContentsState::RestoreContentsFromByteBuffer(env,
505 clazz,
506 state,
507 saved_state_version,
508 initially_hidden));
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(
519 JNIEnv* env,
520 jclass clazz,
521 jstring url,
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,
530 jclass clazz,
531 jobject state,
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,
543 jclass clazz,
544 jobject state,
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
555 // within |state|.
556 static void CreateHistoricalTab(JNIEnv* env,
557 jclass clazz,
558 jobject state,
559 jint saved_state_version) {
560 scoped_ptr<WebContents> web_contents(
561 WebContentsState::RestoreContentsFromByteBuffer(env,
562 clazz,
563 state,
564 saved_state_version,
565 true));
566 if (web_contents.get())
567 TabAndroid::CreateHistoricalTabFromContents(web_contents.get());
570 bool RegisterTabState(JNIEnv* env) {
571 return RegisterNativesImpl(env);