// Copyright (c) 2012 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 "android_webview/browser/state_serializer.h" #include #include #include #include "base/pickle.h" #include "base/time/time.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_entry_restore_context.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/restore_type.h" #include "content/public/browser/web_contents.h" #include "content/public/common/referrer.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/page_state/page_state.h" // Reasons for not re-using TabNavigation under chrome/ as of 20121116: // * Android WebView has different requirements for fields to store since // we are the only ones using values like BaseURLForDataURL. // * TabNavigation does unnecessary copying of data, which in Android // WebView case, is undesired since save/restore is called in Android // very frequently. // * TabNavigation is tightly integrated with the rest of chrome session // restore and sync code, and has other purpose in addition to serializing // NavigationEntry. using std::string; namespace android_webview { namespace { const uint32_t AW_STATE_VERSION = internal::AW_STATE_VERSION_DATA_URL; } // namespace void WriteToPickle(content::WebContents& web_contents, base::Pickle* pickle) { DCHECK(pickle); internal::WriteHeaderToPickle(pickle); content::NavigationController& controller = web_contents.GetController(); const int entry_count = controller.GetEntryCount(); const int selected_entry = controller.GetLastCommittedEntryIndex(); if (blink::features::IsInitialNavigationEntryEnabled()) { // When InitialNavigationEntry is enabled, a NavigationEntry will always // exist, so there will always be at least 1 entry. DCHECK_GE(entry_count, 1); DCHECK_GE(selected_entry, 0); } else { // When InitialNavigationEntry is disabled, there might be 0 entries. DCHECK_GE(entry_count, 0); DCHECK_GE(selected_entry, -1); // -1 is valid } DCHECK_LT(selected_entry, entry_count); pickle->WriteInt(entry_count); pickle->WriteInt(selected_entry); for (int i = 0; i < entry_count; ++i) { internal::WriteNavigationEntryToPickle(*controller.GetEntryAtIndex(i), pickle); } // Please update AW_STATE_VERSION and IsSupportedVersion() if serialization // format is changed. // Make sure the serialization format is updated in a backwards compatible // way. } bool RestoreFromPickle(base::PickleIterator* iterator, content::WebContents* web_contents) { DCHECK(iterator); DCHECK(web_contents); uint32_t state_version = internal::RestoreHeaderFromPickle(iterator); if (!state_version) return false; int entry_count = -1; int selected_entry = -2; // -1 is a valid value if (!iterator->ReadInt(&entry_count)) return false; if (!iterator->ReadInt(&selected_entry)) return false; if (entry_count < 0) return false; if (selected_entry < -1) return false; if (selected_entry >= entry_count) return false; std::unique_ptr context = content::NavigationEntryRestoreContext::Create(); std::vector> entries; entries.reserve(entry_count); for (int i = 0; i < entry_count; ++i) { entries.push_back(content::NavigationEntry::Create()); if (!internal::RestoreNavigationEntryFromPickle( state_version, iterator, entries[i].get(), context.get())) return false; } // |web_contents| takes ownership of these entries after this call. content::NavigationController& controller = web_contents->GetController(); controller.Restore(selected_entry, content::RestoreType::kRestored, &entries); DCHECK_EQ(0u, entries.size()); controller.LoadIfNecessary(); return true; } namespace internal { void WriteHeaderToPickle(base::Pickle* pickle) { WriteHeaderToPickle(AW_STATE_VERSION, pickle); } void WriteHeaderToPickle(uint32_t state_version, base::Pickle* pickle) { pickle->WriteUInt32(state_version); } uint32_t RestoreHeaderFromPickle(base::PickleIterator* iterator) { uint32_t state_version = -1; if (!iterator->ReadUInt32(&state_version)) return 0; if (IsSupportedVersion(state_version)) { return state_version; } return 0; } bool IsSupportedVersion(uint32_t state_version) { return state_version == internal::AW_STATE_VERSION_INITIAL || state_version == internal::AW_STATE_VERSION_DATA_URL; } void WriteNavigationEntryToPickle(content::NavigationEntry& entry, base::Pickle* pickle) { WriteNavigationEntryToPickle(AW_STATE_VERSION, entry, pickle); } void WriteNavigationEntryToPickle(uint32_t state_version, content::NavigationEntry& entry, base::Pickle* pickle) { DCHECK(IsSupportedVersion(state_version)); pickle->WriteString(entry.GetURL().spec()); pickle->WriteString(entry.GetVirtualURL().spec()); const content::Referrer& referrer = entry.GetReferrer(); pickle->WriteString(referrer.url.spec()); pickle->WriteInt(static_cast(referrer.policy)); pickle->WriteString16(entry.GetTitle()); pickle->WriteString(entry.GetPageState().ToEncodedData()); pickle->WriteBool(static_cast(entry.GetHasPostData())); pickle->WriteString(entry.GetOriginalRequestURL().spec()); pickle->WriteString(entry.GetBaseURLForDataURL().spec()); if (state_version >= internal::AW_STATE_VERSION_DATA_URL) { const char* data = nullptr; size_t size = 0; const scoped_refptr& s = entry.GetDataURLAsString(); if (s) { data = s->front_as(); size = s->size(); } // Even when |entry.GetDataForDataURL()| is null we still need to write a // zero-length entry to ensure the fields all line up when read back in. pickle->WriteData(data, size); } pickle->WriteBool(static_cast(entry.GetIsOverridingUserAgent())); pickle->WriteInt64(entry.GetTimestamp().ToInternalValue()); pickle->WriteInt(entry.GetHttpStatusCode()); // Please update AW_STATE_VERSION and IsSupportedVersion() if serialization // format is changed. // Make sure the serialization format is updated in a backwards compatible // way. } bool RestoreNavigationEntryFromPickle( base::PickleIterator* iterator, content::NavigationEntry* entry, content::NavigationEntryRestoreContext* context) { return RestoreNavigationEntryFromPickle(AW_STATE_VERSION, iterator, entry, context); } bool RestoreNavigationEntryFromPickle( uint32_t state_version, base::PickleIterator* iterator, content::NavigationEntry* entry, content::NavigationEntryRestoreContext* context) { DCHECK(IsSupportedVersion(state_version)); DCHECK(iterator); DCHECK(entry); DCHECK(context); GURL deserialized_url; { string url; if (!iterator->ReadString(&url)) return false; deserialized_url = GURL(url); // Note: The url will be cloberred by the SetPageState call below (see how // RecursivelyGenerateFrameEntries uses PageState data to create // FrameNavigationEntries). // // Nevertheless, we call SetURL here to temporarily set the URL, because it // modifies the state that might be depended on in some calls below (e.g. // the SetVirtualURL call). entry->SetURL(deserialized_url); } { string virtual_url; if (!iterator->ReadString(&virtual_url)) return false; entry->SetVirtualURL(GURL(virtual_url)); } content::Referrer deserialized_referrer; { // Note: The referrer will be cloberred by the SetPageState call below (see // how RecursivelyGenerateFrameEntries uses PageState data to create // FrameNavigationEntries). string referrer_url; int policy; if (!iterator->ReadString(&referrer_url)) return false; if (!iterator->ReadInt(&policy)) return false; deserialized_referrer.url = GURL(referrer_url); deserialized_referrer.policy = content::Referrer::ConvertToPolicy(policy); } { std::u16string title; if (!iterator->ReadString16(&title)) return false; entry->SetTitle(title); } { string content_state; if (!iterator->ReadString(&content_state)) return false; // In legacy output of WebViewProvider.saveState, the |content_state| // might be empty - we need to gracefully handle such data when // it is deserialized via WebViewProvider.restoreState. if (content_state.empty()) { // Ensure that the deserialized/restored content::NavigationEntry (and // the content::FrameNavigationEntry underneath) has a valid PageState. entry->SetPageState(blink::PageState::CreateFromURL(deserialized_url), context); // The |deserialized_referrer| might be inconsistent with the referrer // embedded inside the PageState set above. Nevertheless, to minimize // changes to behavior of old session restore entries, we restore the // deserialized referrer here. // // TODO(lukasza): Consider including the |deserialized_referrer| in the // PageState set above + drop the SetReferrer call below. This will // slightly change the legacy behavior, but will make PageState and // Referrer consistent. entry->SetReferrer(deserialized_referrer); } else { // Note that PageState covers and will clobber some of the values covered // by data within |iterator| (e.g. URL and referrer). entry->SetPageState( blink::PageState::CreateFromEncodedData(content_state), context); // |deserialized_url| and |deserialized_referrer| are redundant wrt // PageState, but they should be consistent / in-sync. DCHECK_EQ(deserialized_url, entry->GetURL()); DCHECK_EQ(deserialized_referrer.url, entry->GetReferrer().url); DCHECK_EQ(deserialized_referrer.policy, entry->GetReferrer().policy); } } { bool has_post_data; if (!iterator->ReadBool(&has_post_data)) return false; entry->SetHasPostData(has_post_data); } { string original_request_url; if (!iterator->ReadString(&original_request_url)) return false; entry->SetOriginalRequestURL(GURL(original_request_url)); } { string base_url_for_data_url; if (!iterator->ReadString(&base_url_for_data_url)) return false; entry->SetBaseURLForDataURL(GURL(base_url_for_data_url)); } if (state_version >= internal::AW_STATE_VERSION_DATA_URL) { const char* data; size_t size; if (!iterator->ReadData(&data, &size)) return false; if (size > 0) { scoped_refptr ref = new base::RefCountedString(); ref->data().assign(data, size); entry->SetDataURLAsString(ref); } } { bool is_overriding_user_agent; if (!iterator->ReadBool(&is_overriding_user_agent)) return false; entry->SetIsOverridingUserAgent(is_overriding_user_agent); } { int64_t timestamp; if (!iterator->ReadInt64(×tamp)) return false; entry->SetTimestamp(base::Time::FromInternalValue(timestamp)); } { int http_status_code; if (!iterator->ReadInt(&http_status_code)) return false; entry->SetHttpStatusCode(http_status_code); } return true; } } // namespace internal } // namespace android_webview